feat(cleanup): protect pinned issues from cleanup/compact (bd-b2k)

Add Pinned field to Issue struct and database schema to protect
issues from accidental deletion via cleanup or compaction.

Changes:
- Add Pinned bool field to types.Issue
- Create migration 023_pinned_column.go for database schema
- Filter out pinned issues in cleanup command before deletion
- Add pinned check to GetTier1Candidates and GetTier2Candidates
- Add pinned check to CheckEligibility for compaction
- Update all SQL queries and scan functions to include pinned field

🤖 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:42:35 -08:00
parent 9dc34da64a
commit f032eccbad
13 changed files with 152 additions and 35 deletions

View File

@@ -32,14 +32,19 @@ func insertIssue(ctx context.Context, conn *sql.Conn, issue *types.Issue) error
ephemeral = 1
}
pinned := 0
if issue.Pinned {
pinned = 1
}
_, err := conn.ExecContext(ctx, `
INSERT OR IGNORE INTO issues (
id, content_hash, title, description, design, acceptance_criteria, notes,
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
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
sender, ephemeral, pinned
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`,
issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design,
issue.AcceptanceCriteria, issue.Notes, issue.Status,
@@ -47,7 +52,7 @@ func insertIssue(ctx context.Context, conn *sql.Conn, issue *types.Issue) error
issue.EstimatedMinutes, issue.CreatedAt, issue.UpdatedAt,
issue.ClosedAt, issue.ExternalRef, sourceRepo, issue.CloseReason,
issue.DeletedAt, issue.DeletedBy, issue.DeleteReason, issue.OriginalType,
issue.Sender, ephemeral,
issue.Sender, ephemeral, pinned,
)
if err != nil {
// INSERT OR IGNORE should handle duplicates, but driver may still return error
@@ -68,8 +73,8 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er
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
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
sender, ephemeral, pinned
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`)
if err != nil {
return fmt.Errorf("failed to prepare statement: %w", err)
@@ -87,6 +92,11 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er
ephemeral = 1
}
pinned := 0
if issue.Pinned {
pinned = 1
}
_, err = stmt.ExecContext(ctx,
issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design,
issue.AcceptanceCriteria, issue.Notes, issue.Status,
@@ -94,7 +104,7 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er
issue.EstimatedMinutes, issue.CreatedAt, issue.UpdatedAt,
issue.ClosedAt, issue.ExternalRef, sourceRepo, issue.CloseReason,
issue.DeletedAt, issue.DeletedBy, issue.DeleteReason, issue.OriginalType,
issue.Sender, ephemeral,
issue.Sender, ephemeral, pinned,
)
if err != nil {
// INSERT OR IGNORE should handle duplicates, but driver may still return error