feat(tombstone): add P2 code review improvements (bd-saa, bd-1bu, bd-nyt)
- Add partial index on deleted_at for efficient TTL queries - Exclude tombstones from SearchIssues by default (new IncludeTombstones filter) - Report tombstone count separately in GetStatistics - Display tombstone count in bd stats output 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -269,6 +269,9 @@ var statsCmd = &cobra.Command{
|
|||||||
fmt.Printf("Closed: %d\n", stats.ClosedIssues)
|
fmt.Printf("Closed: %d\n", stats.ClosedIssues)
|
||||||
fmt.Printf("Blocked: %d\n", stats.BlockedIssues)
|
fmt.Printf("Blocked: %d\n", stats.BlockedIssues)
|
||||||
fmt.Printf("Ready: %s\n", green(fmt.Sprintf("%d", stats.ReadyIssues)))
|
fmt.Printf("Ready: %s\n", green(fmt.Sprintf("%d", stats.ReadyIssues)))
|
||||||
|
if stats.TombstoneIssues > 0 {
|
||||||
|
fmt.Printf("Deleted: %d (tombstones)\n", stats.TombstoneIssues)
|
||||||
|
}
|
||||||
if stats.AverageLeadTime > 0 {
|
if stats.AverageLeadTime > 0 {
|
||||||
fmt.Printf("Avg Lead Time: %.1f hours\n", stats.AverageLeadTime)
|
fmt.Printf("Avg Lead Time: %.1f hours\n", stats.AverageLeadTime)
|
||||||
}
|
}
|
||||||
@@ -307,6 +310,9 @@ var statsCmd = &cobra.Command{
|
|||||||
fmt.Printf("Closed: %d\n", stats.ClosedIssues)
|
fmt.Printf("Closed: %d\n", stats.ClosedIssues)
|
||||||
fmt.Printf("Blocked: %d\n", stats.BlockedIssues)
|
fmt.Printf("Blocked: %d\n", stats.BlockedIssues)
|
||||||
fmt.Printf("Ready: %s\n", green(fmt.Sprintf("%d", stats.ReadyIssues)))
|
fmt.Printf("Ready: %s\n", green(fmt.Sprintf("%d", stats.ReadyIssues)))
|
||||||
|
if stats.TombstoneIssues > 0 {
|
||||||
|
fmt.Printf("Deleted: %d (tombstones)\n", stats.TombstoneIssues)
|
||||||
|
}
|
||||||
if stats.EpicsEligibleForClosure > 0 {
|
if stats.EpicsEligibleForClosure > 0 {
|
||||||
fmt.Printf("Epics Ready to Close: %s\n", green(fmt.Sprintf("%d", stats.EpicsEligibleForClosure)))
|
fmt.Printf("Epics Ready to Close: %s\n", green(fmt.Sprintf("%d", stats.EpicsEligibleForClosure)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,15 +110,16 @@ func (s *SQLiteStorage) GetEvents(ctx context.Context, issueID string, limit int
|
|||||||
func (s *SQLiteStorage) GetStatistics(ctx context.Context) (*types.Statistics, error) {
|
func (s *SQLiteStorage) GetStatistics(ctx context.Context) (*types.Statistics, error) {
|
||||||
var stats types.Statistics
|
var stats types.Statistics
|
||||||
|
|
||||||
// Get counts
|
// Get counts (bd-nyt: exclude tombstones from TotalIssues, report separately)
|
||||||
err := s.db.QueryRowContext(ctx, `
|
err := s.db.QueryRowContext(ctx, `
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) as total,
|
COALESCE(SUM(CASE WHEN status != 'tombstone' THEN 1 ELSE 0 END), 0) as total,
|
||||||
COALESCE(SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END), 0) as open,
|
COALESCE(SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END), 0) as open,
|
||||||
COALESCE(SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END), 0) as in_progress,
|
COALESCE(SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END), 0) as in_progress,
|
||||||
COALESCE(SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END), 0) as closed
|
COALESCE(SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END), 0) as closed,
|
||||||
|
COALESCE(SUM(CASE WHEN status = 'tombstone' THEN 1 ELSE 0 END), 0) as tombstone
|
||||||
FROM issues
|
FROM issues
|
||||||
`).Scan(&stats.TotalIssues, &stats.OpenIssues, &stats.InProgressIssues, &stats.ClosedIssues)
|
`).Scan(&stats.TotalIssues, &stats.OpenIssues, &stats.InProgressIssues, &stats.ClosedIssues, &stats.TombstoneIssues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get issue counts: %w", err)
|
return nil, fmt.Errorf("failed to get issue counts: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,5 +43,12 @@ func MigrateTombstoneColumns(db *sql.DB) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add partial index on deleted_at for efficient TTL queries (bd-saa)
|
||||||
|
// Only indexes non-NULL values, making it very efficient for tombstone filtering
|
||||||
|
_, err := db.Exec(`CREATE INDEX IF NOT EXISTS idx_issues_deleted_at ON issues(deleted_at) WHERE deleted_at IS NOT NULL`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create deleted_at index: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1186,6 +1186,10 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
|
|||||||
if filter.Status != nil {
|
if filter.Status != nil {
|
||||||
whereClauses = append(whereClauses, "status = ?")
|
whereClauses = append(whereClauses, "status = ?")
|
||||||
args = append(args, *filter.Status)
|
args = append(args, *filter.Status)
|
||||||
|
} else if !filter.IncludeTombstones {
|
||||||
|
// Exclude tombstones by default unless explicitly filtering for them (bd-1bu)
|
||||||
|
whereClauses = append(whereClauses, "status != ?")
|
||||||
|
args = append(args, types.StatusTombstone)
|
||||||
}
|
}
|
||||||
|
|
||||||
if filter.Priority != nil {
|
if filter.Priority != nil {
|
||||||
|
|||||||
@@ -919,6 +919,10 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter
|
|||||||
if filter.Status != nil {
|
if filter.Status != nil {
|
||||||
whereClauses = append(whereClauses, "status = ?")
|
whereClauses = append(whereClauses, "status = ?")
|
||||||
args = append(args, *filter.Status)
|
args = append(args, *filter.Status)
|
||||||
|
} else if !filter.IncludeTombstones {
|
||||||
|
// Exclude tombstones by default unless explicitly filtering for them (bd-1bu)
|
||||||
|
whereClauses = append(whereClauses, "status != ?")
|
||||||
|
args = append(args, types.StatusTombstone)
|
||||||
}
|
}
|
||||||
|
|
||||||
if filter.Priority != nil {
|
if filter.Priority != nil {
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ type Statistics struct {
|
|||||||
ClosedIssues int `json:"closed_issues"`
|
ClosedIssues int `json:"closed_issues"`
|
||||||
BlockedIssues int `json:"blocked_issues"`
|
BlockedIssues int `json:"blocked_issues"`
|
||||||
ReadyIssues int `json:"ready_issues"`
|
ReadyIssues int `json:"ready_issues"`
|
||||||
|
TombstoneIssues int `json:"tombstone_issues"` // Soft-deleted issues (bd-nyt)
|
||||||
EpicsEligibleForClosure int `json:"epics_eligible_for_closure"`
|
EpicsEligibleForClosure int `json:"epics_eligible_for_closure"`
|
||||||
AverageLeadTime float64 `json:"average_lead_time_hours"`
|
AverageLeadTime float64 `json:"average_lead_time_hours"`
|
||||||
}
|
}
|
||||||
@@ -334,6 +335,9 @@ type IssueFilter struct {
|
|||||||
// Numeric ranges
|
// Numeric ranges
|
||||||
PriorityMin *int
|
PriorityMin *int
|
||||||
PriorityMax *int
|
PriorityMax *int
|
||||||
|
|
||||||
|
// Tombstone filtering (bd-1bu)
|
||||||
|
IncludeTombstones bool // If false (default), exclude tombstones from results
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortPolicy determines how ready work is ordered
|
// SortPolicy determines how ready work is ordered
|
||||||
|
|||||||
Reference in New Issue
Block a user