feat(list): add --pinned and --no-pinned filter flags

Add ability to filter issues by their pinned status in bd list command.

- Add pinned column to issues table via migration 023
- Add Pinned field to Issue struct and IssueFilter
- Update all storage layer queries to include pinned column
- Add --pinned flag to show only pinned issues
- Add --no-pinned flag to exclude pinned issues
- Update RPC layer to forward pinned filter to daemon mode
- Add pinned to allowedUpdateFields for bd update support

Resolves: beads-p8e

🤖 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-19 00:46:46 -08:00
parent 9dc34da64a
commit f6930eb399
15 changed files with 168 additions and 27 deletions

View File

@@ -248,6 +248,8 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
// Messaging fields (bd-kwro)
var sender sql.NullString
var ephemeral sql.NullInt64
// Pinned flag (bd-p8e)
var pinned sql.NullInt64
var contentHash sql.NullString
var compactedAtCommit sql.NullString
@@ -257,7 +259,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
created_at, updated_at, closed_at, external_ref,
compaction_level, compacted_at, compacted_at_commit, original_size, source_repo, close_reason,
deleted_at, deleted_by, delete_reason, original_type,
sender, ephemeral
sender, ephemeral, pinned
FROM issues
WHERE id = ?
`, id).Scan(
@@ -267,7 +269,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
&issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef,
&issue.CompactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &sourceRepo, &closeReason,
&deletedAt, &deletedBy, &deleteReason, &originalType,
&sender, &ephemeral,
&sender, &ephemeral, &pinned,
)
if err == sql.ErrNoRows {
@@ -325,6 +327,10 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
if ephemeral.Valid && ephemeral.Int64 != 0 {
issue.Ephemeral = true
}
// Pinned flag (bd-p8e)
if pinned.Valid && pinned.Int64 != 0 {
issue.Pinned = true
}
// Fetch labels for this issue
labels, err := s.GetLabels(ctx, issue.ID)
@@ -431,6 +437,8 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
// Messaging fields (bd-kwro)
var sender sql.NullString
var ephemeral sql.NullInt64
// Pinned flag (bd-p8e)
var pinned sql.NullInt64
err := s.db.QueryRowContext(ctx, `
SELECT id, content_hash, title, description, design, acceptance_criteria, notes,
@@ -438,7 +446,7 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
created_at, updated_at, closed_at, external_ref,
compaction_level, compacted_at, compacted_at_commit, original_size, source_repo, close_reason,
deleted_at, deleted_by, delete_reason, original_type,
sender, ephemeral
sender, ephemeral, pinned
FROM issues
WHERE external_ref = ?
`, externalRef).Scan(
@@ -448,7 +456,7 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
&issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRefCol,
&issue.CompactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &sourceRepo, &closeReason,
&deletedAt, &deletedBy, &deleteReason, &originalType,
&sender, &ephemeral,
&sender, &ephemeral, &pinned,
)
if err == sql.ErrNoRows {
@@ -506,6 +514,10 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
if ephemeral.Valid && ephemeral.Int64 != 0 {
issue.Ephemeral = true
}
// Pinned flag (bd-p8e)
if pinned.Valid && pinned.Int64 != 0 {
issue.Pinned = true
}
// Fetch labels for this issue
labels, err := s.GetLabels(ctx, issue.ID)
@@ -536,6 +548,8 @@ var allowedUpdateFields = map[string]bool{
"ephemeral": true,
// NOTE: replies_to, relates_to, duplicate_of, superseded_by removed per Decision 004
// Use AddDependency() to create graph edges instead
// Pinned flag (bd-p8e)
"pinned": true,
}
// validatePriority validates a priority value
@@ -1547,6 +1561,15 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
}
}
// Pinned filtering (bd-p8e)
if filter.Pinned != nil {
if *filter.Pinned {
whereClauses = append(whereClauses, "pinned = 1")
} else {
whereClauses = append(whereClauses, "(pinned = 0 OR pinned IS NULL)")
}
}
whereSQL := ""
if len(whereClauses) > 0 {
whereSQL = "WHERE " + strings.Join(whereClauses, " AND ")
@@ -1564,7 +1587,7 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
status, priority, issue_type, assignee, estimated_minutes,
created_at, updated_at, closed_at, external_ref, source_repo, close_reason,
deleted_at, deleted_by, delete_reason, original_type,
sender, ephemeral
sender, ephemeral, pinned
FROM issues
%s
ORDER BY priority ASC, created_at DESC