chore: remove issue ID references from comments and changelogs
Strip (bd-xxx), (gt-xxx) suffixes from code comments and changelog entries. The descriptions remain meaningful without the ephemeral issue IDs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ func validateBatchIssues(issues []*types.Issue) error {
|
||||
}
|
||||
|
||||
// validateBatchIssuesWithCustomStatuses validates all issues in a batch,
|
||||
// allowing custom statuses in addition to built-in ones (bd-1pj6).
|
||||
// allowing custom statuses in addition to built-in ones.
|
||||
func validateBatchIssuesWithCustomStatuses(issues []*types.Issue, customStatuses []string) error {
|
||||
now := time.Now()
|
||||
for i, issue := range issues {
|
||||
@@ -67,7 +67,7 @@ func (s *SQLiteStorage) generateBatchIDs(ctx context.Context, conn *sql.Conn, is
|
||||
var prefix string
|
||||
err := conn.QueryRowContext(ctx, `SELECT value FROM config WHERE key = ?`, "issue_prefix").Scan(&prefix)
|
||||
if err == sql.ErrNoRows || prefix == "" {
|
||||
// CRITICAL: Reject operation if issue_prefix config is missing (bd-166)
|
||||
// CRITICAL: Reject operation if issue_prefix config is missing
|
||||
return fmt.Errorf("database not initialized: issue_prefix config is missing (run 'bd init --prefix <prefix>' first)")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to get config: %w", err)
|
||||
@@ -214,7 +214,7 @@ func checkForExistingIDs(ctx context.Context, conn *sql.Conn, issues []*types.Is
|
||||
// }
|
||||
//
|
||||
// // After importing with explicit IDs, sync counters to prevent collisions
|
||||
// REMOVED (bd-c7af): SyncAllCounters example - no longer needed with hash IDs
|
||||
// REMOVED: SyncAllCounters example - no longer needed with hash IDs
|
||||
//
|
||||
// Performance:
|
||||
// - 100 issues: ~30ms (vs ~900ms with CreateIssue loop)
|
||||
@@ -250,7 +250,7 @@ func (s *SQLiteStorage) CreateIssuesWithFullOptions(ctx context.Context, issues
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch custom statuses for validation (bd-1pj6)
|
||||
// Fetch custom statuses for validation
|
||||
customStatuses, err := s.GetCustomStatuses(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get custom statuses: %w", err)
|
||||
@@ -268,7 +268,7 @@ func (s *SQLiteStorage) CreateIssuesWithFullOptions(ctx context.Context, issues
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
// Use retry logic with exponential backoff to handle SQLITE_BUSY under concurrent load (bd-ola6)
|
||||
// Use retry logic with exponential backoff to handle SQLITE_BUSY under concurrent load
|
||||
if err := beginImmediateWithRetry(ctx, conn, 5, 10*time.Millisecond); err != nil {
|
||||
return fmt.Errorf("failed to begin immediate transaction: %w", err)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (s *SQLiteStorage) AddDependency(ctx context.Context, dep *types.Dependency
|
||||
return fmt.Errorf("issue %s not found", dep.IssueID)
|
||||
}
|
||||
|
||||
// External refs (external:<project>:<capability>) don't need target validation (bd-zmmy)
|
||||
// External refs (external:<project>:<capability>) don't need target validation
|
||||
// They are resolved lazily at query time by CheckExternalDep
|
||||
isExternalRef := strings.HasPrefix(dep.DependsOnID, "external:")
|
||||
|
||||
@@ -169,7 +169,7 @@ func (s *SQLiteStorage) AddDependency(ctx context.Context, dep *types.Dependency
|
||||
return wrapDBError("mark issues dirty after adding dependency", err)
|
||||
}
|
||||
|
||||
// Invalidate blocked issues cache since dependencies changed (bd-5qim)
|
||||
// Invalidate blocked issues cache since dependencies changed
|
||||
// Only invalidate for types that affect ready work calculation
|
||||
if dep.Type.AffectsReadyWork() {
|
||||
if err := s.invalidateBlockedCache(ctx, tx); err != nil {
|
||||
@@ -231,7 +231,7 @@ func (s *SQLiteStorage) RemoveDependency(ctx context.Context, issueID, dependsOn
|
||||
return wrapDBError("mark issues dirty after removing dependency", err)
|
||||
}
|
||||
|
||||
// Invalidate blocked issues cache if this was a blocking dependency (bd-5qim)
|
||||
// Invalidate blocked issues cache if this was a blocking dependency
|
||||
if needsCacheInvalidation {
|
||||
if err := s.invalidateBlockedCache(ctx, tx); err != nil {
|
||||
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
|
||||
@@ -627,7 +627,7 @@ func (s *SQLiteStorage) GetDependencyTree(ctx context.Context, issueID string, m
|
||||
nodes = append(nodes, &node)
|
||||
}
|
||||
|
||||
// Fetch external dependencies for all issues in the tree (bd-vks2)
|
||||
// Fetch external dependencies for all issues in the tree
|
||||
// External deps like "external:project:capability" don't exist in the issues
|
||||
// table, so the recursive CTE above doesn't find them. We add them as
|
||||
// synthetic leaf nodes here.
|
||||
@@ -871,14 +871,14 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
|
||||
var deletedBy sql.NullString
|
||||
var deleteReason sql.NullString
|
||||
var originalType sql.NullString
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
var sender sql.NullString
|
||||
var wisp sql.NullInt64
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
var pinned sql.NullInt64
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
var timeoutNs sql.NullInt64
|
||||
@@ -929,22 +929,22 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
|
||||
if originalType.Valid {
|
||||
issue.OriginalType = originalType.String
|
||||
}
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
if sender.Valid {
|
||||
issue.Sender = sender.String
|
||||
}
|
||||
if wisp.Valid && wisp.Int64 != 0 {
|
||||
issue.Ephemeral = true
|
||||
}
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
if pinned.Valid && pinned.Int64 != 0 {
|
||||
issue.Pinned = true
|
||||
}
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
}
|
||||
@@ -993,14 +993,14 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows *
|
||||
var deletedBy sql.NullString
|
||||
var deleteReason sql.NullString
|
||||
var originalType sql.NullString
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
var sender sql.NullString
|
||||
var wisp sql.NullInt64
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
var pinned sql.NullInt64
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
var timeoutNs sql.NullInt64
|
||||
@@ -1050,22 +1050,22 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows *
|
||||
if originalType.Valid {
|
||||
issue.OriginalType = originalType.String
|
||||
}
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
if sender.Valid {
|
||||
issue.Sender = sender.String
|
||||
}
|
||||
if wisp.Valid && wisp.Int64 != 0 {
|
||||
issue.Ephemeral = true
|
||||
}
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
if pinned.Valid && pinned.Int64 != 0 {
|
||||
issue.Pinned = true
|
||||
}
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
}
|
||||
|
||||
@@ -85,19 +85,19 @@ func getMigrationDescription(name string) string {
|
||||
"source_repo_column": "Adds source_repo column for multi-repo support",
|
||||
"repo_mtimes_table": "Adds repo_mtimes table for multi-repo hydration caching",
|
||||
"child_counters_table": "Adds child_counters table for hierarchical ID generation with ON DELETE CASCADE",
|
||||
"blocked_issues_cache": "Adds blocked_issues_cache table for GetReadyWork performance optimization (bd-5qim)",
|
||||
"orphan_detection": "Detects orphaned child issues and logs them for user action (bd-3852)",
|
||||
"close_reason_column": "Adds close_reason column to issues table for storing closure explanations (bd-uyu)",
|
||||
"tombstone_columns": "Adds tombstone columns (deleted_at, deleted_by, delete_reason, original_type) for inline soft-delete (bd-vw8)",
|
||||
"messaging_fields": "Adds messaging fields (sender, ephemeral, replies_to, relates_to, duplicate_of, superseded_by) for inter-agent communication (bd-kwro)",
|
||||
"blocked_issues_cache": "Adds blocked_issues_cache table for GetReadyWork performance optimization",
|
||||
"orphan_detection": "Detects orphaned child issues and logs them for user action",
|
||||
"close_reason_column": "Adds close_reason column to issues table for storing closure explanations",
|
||||
"tombstone_columns": "Adds tombstone columns (deleted_at, deleted_by, delete_reason, original_type) for inline soft-delete",
|
||||
"messaging_fields": "Adds messaging fields (sender, ephemeral, replies_to, relates_to, duplicate_of, superseded_by) for inter-agent communication",
|
||||
"edge_consolidation": "Adds metadata and thread_id columns to dependencies table for edge schema consolidation (Decision 004)",
|
||||
"migrate_edge_fields": "Migrates existing issue fields (replies_to, relates_to, duplicate_of, superseded_by) to dependency edges (Decision 004 Phase 3)",
|
||||
"drop_edge_columns": "Drops deprecated edge columns (replies_to, relates_to, duplicate_of, superseded_by) from issues table (Decision 004 Phase 4)",
|
||||
"pinned_column": "Adds pinned column for persistent context markers (bd-7h5)",
|
||||
"is_template_column": "Adds is_template column for template molecules (beads-1ra)",
|
||||
"remove_depends_on_fk": "Removes FK constraint on depends_on_id to allow external references (bd-zmmy)",
|
||||
"additional_indexes": "Adds performance optimization indexes for common query patterns (bd-h0we)",
|
||||
"gate_columns": "Adds gate columns (await_type, await_id, timeout_ns, waiters) for async coordination (bd-udsi)",
|
||||
"pinned_column": "Adds pinned column for persistent context markers",
|
||||
"is_template_column": "Adds is_template column for template molecules",
|
||||
"remove_depends_on_fk": "Removes FK constraint on depends_on_id to allow external references",
|
||||
"additional_indexes": "Adds performance optimization indexes for common query patterns",
|
||||
"gate_columns": "Adds gate columns (await_type, await_id, timeout_ns, waiters) for async coordination",
|
||||
}
|
||||
|
||||
if desc, ok := descriptions[name]; ok {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
// MigrateBlockedIssuesCache creates the blocked_issues_cache table for performance optimization
|
||||
// This cache materializes the recursive CTE computation from GetReadyWork to avoid
|
||||
// expensive recursive queries on every call (bd-5qim)
|
||||
// expensive recursive queries on every call
|
||||
func MigrateBlockedIssuesCache(db *sql.DB) error {
|
||||
// Check if table already exists
|
||||
var tableName string
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// MigrateTombstoneColumns adds tombstone support columns to the issues table.
|
||||
// These columns support inline soft-delete (bd-vw8) replacing deletions.jsonl:
|
||||
// These columns support inline soft-delete, replacing deletions.jsonl:
|
||||
// - deleted_at: when the issue was deleted
|
||||
// - deleted_by: who deleted the issue
|
||||
// - delete_reason: why the issue was deleted
|
||||
@@ -43,7 +43,7 @@ func MigrateTombstoneColumns(db *sql.DB) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Add partial index on deleted_at for efficient TTL queries (bd-saa)
|
||||
// Add partial index on deleted_at for efficient TTL queries
|
||||
// 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 {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// MigrateMessagingFields adds messaging and graph link support columns to the issues table.
|
||||
// These columns support inter-agent communication (bd-kwro):
|
||||
// These columns support inter-agent communication:
|
||||
// - sender: who sent this message
|
||||
// - ephemeral: can be bulk-deleted when closed
|
||||
// - replies_to: issue ID for conversation threading
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// MigratePinnedColumn adds the pinned column to the issues table.
|
||||
// Pinned issues are persistent context markers that should not be treated as work items (bd-7h5).
|
||||
// Pinned issues are persistent context markers that should not be treated as work items.
|
||||
func MigratePinnedColumn(db *sql.DB) error {
|
||||
// Check if column already exists
|
||||
var columnExists bool
|
||||
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
)
|
||||
|
||||
// MigrateAdditionalIndexes adds performance optimization indexes identified
|
||||
// during schema review (bd-h0we).
|
||||
// during schema review.
|
||||
//
|
||||
// Indexes added:
|
||||
// - idx_issues_updated_at: For GetStaleIssues date filtering (bd-bha9)
|
||||
// - idx_issues_status_priority: For common list query patterns (bd-a9y3)
|
||||
// - idx_labels_label_issue: Covering index for label lookups (bd-jke6)
|
||||
// - idx_dependencies_issue_type: For blocked issues queries (bd-8x3w)
|
||||
// - idx_events_issue_type: For close reason queries (bd-lk39)
|
||||
// - idx_issues_updated_at: For GetStaleIssues date filtering
|
||||
// - idx_issues_status_priority: For common list query patterns
|
||||
// - idx_labels_label_issue: Covering index for label lookups
|
||||
// - idx_dependencies_issue_type: For blocked issues queries
|
||||
// - idx_events_issue_type: For close reason queries
|
||||
func MigrateAdditionalIndexes(db *sql.DB) error {
|
||||
indexes := []struct {
|
||||
name string
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// MigrateGateColumns adds gate-related columns to the issues table for async coordination (bd-udsi).
|
||||
// MigrateGateColumns adds gate-related columns to the issues table for async coordination.
|
||||
// Gate fields enable agents to wait on external conditions (CI completion, human approval, etc.)
|
||||
func MigrateGateColumns(db *sql.DB) error {
|
||||
columns := []struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// MigrateAgentFields adds agent-specific fields to the issues table.
|
||||
// These fields support the agent-as-bead pattern (gt-v2gkv, gt-h5sza):
|
||||
// These fields support the agent-as-bead pattern:
|
||||
// - hook_bead: current work attached to agent's hook (0..1 cardinality)
|
||||
// - role_bead: reference to role definition bead
|
||||
// - agent_state: agent-reported state (idle|running|stuck|stopped)
|
||||
|
||||
@@ -59,23 +59,23 @@ func formatJSONStringArray(arr []string) string {
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// REMOVED (bd-8e05): getNextIDForPrefix and AllocateNextID - sequential ID generation
|
||||
// REMOVED: getNextIDForPrefix and AllocateNextID - sequential ID generation
|
||||
// no longer needed with hash-based IDs
|
||||
// Migration functions moved to migrations.go (bd-fc2d, bd-b245)
|
||||
// Migration functions moved to migrations.go
|
||||
|
||||
// getNextChildNumber atomically generates the next child number for a parent ID
|
||||
// Uses the child_counters table for atomic, cross-process child ID generation
|
||||
// Hash ID generation functions moved to hash_ids.go (bd-90a5)
|
||||
// Hash ID generation functions moved to hash_ids.go
|
||||
|
||||
// REMOVED (bd-c7af): SyncAllCounters - no longer needed with hash IDs
|
||||
// REMOVED: SyncAllCounters - no longer needed with hash IDs
|
||||
|
||||
// REMOVED (bd-166): derivePrefixFromPath was causing duplicate issues with wrong prefix
|
||||
// REMOVED: derivePrefixFromPath was causing duplicate issues with wrong prefix
|
||||
// The database should ALWAYS have issue_prefix config set explicitly (by 'bd init' or auto-import)
|
||||
// Never derive prefix from filename - it leads to silent data corruption
|
||||
|
||||
// CreateIssue creates a new issue
|
||||
func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, actor string) error {
|
||||
// Fetch custom statuses for validation (bd-1pj6)
|
||||
// Fetch custom statuses for validation
|
||||
customStatuses, err := s.GetCustomStatuses(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get custom statuses: %w", err)
|
||||
@@ -116,7 +116,7 @@ func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
|
||||
return fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Compute content hash (bd-95)
|
||||
// Compute content hash
|
||||
if issue.ContentHash == "" {
|
||||
issue.ContentHash = issue.ComputeContentHash()
|
||||
}
|
||||
@@ -138,7 +138,7 @@ func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
|
||||
// We use raw Exec instead of BeginTx because database/sql doesn't support transaction
|
||||
// modes in BeginTx, and modernc.org/sqlite's BeginTx always uses DEFERRED mode.
|
||||
//
|
||||
// Use retry logic with exponential backoff to handle SQLITE_BUSY under concurrent load (bd-ola6)
|
||||
// Use retry logic with exponential backoff to handle SQLITE_BUSY under concurrent load
|
||||
if err := beginImmediateWithRetry(ctx, conn, 5, 10*time.Millisecond); err != nil {
|
||||
return fmt.Errorf("failed to begin immediate transaction: %w", err)
|
||||
}
|
||||
@@ -156,14 +156,14 @@ func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
|
||||
var configPrefix string
|
||||
err = conn.QueryRowContext(ctx, `SELECT value FROM config WHERE key = ?`, "issue_prefix").Scan(&configPrefix)
|
||||
if err == sql.ErrNoRows || configPrefix == "" {
|
||||
// CRITICAL: Reject operation if issue_prefix config is missing (bd-166)
|
||||
// CRITICAL: Reject operation if issue_prefix config is missing
|
||||
// This prevents duplicate issues with wrong prefix
|
||||
return fmt.Errorf("database not initialized: issue_prefix config is missing (run 'bd init --prefix <prefix>' first)")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to get config: %w", err)
|
||||
}
|
||||
|
||||
// Use IDPrefix override if set, combined with config prefix (bd-hobo)
|
||||
// Use IDPrefix override if set, combined with config prefix
|
||||
// e.g., configPrefix="bd" + IDPrefix="wisp" → "bd-wisp"
|
||||
prefix := configPrefix
|
||||
if issue.IDPrefix != "" {
|
||||
@@ -172,14 +172,14 @@ func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
|
||||
|
||||
// Generate or validate ID
|
||||
if issue.ID == "" {
|
||||
// Generate hash-based ID with adaptive length based on database size (bd-ea2a13)
|
||||
// Generate hash-based ID with adaptive length based on database size
|
||||
generatedID, err := GenerateIssueID(ctx, conn, prefix, issue, actor)
|
||||
if err != nil {
|
||||
return wrapDBError("generate issue ID", err)
|
||||
}
|
||||
issue.ID = generatedID
|
||||
} else {
|
||||
// Validate that explicitly provided ID matches the configured prefix (bd-177)
|
||||
// Validate that explicitly provided ID matches the configured prefix
|
||||
if err := ValidateIssueIDPrefix(issue.ID, prefix); err != nil {
|
||||
return wrapDBError("validate issue ID prefix", err)
|
||||
}
|
||||
@@ -235,7 +235,7 @@ func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
|
||||
}
|
||||
|
||||
// validateBatchIssues validates all issues in a batch and sets timestamps
|
||||
// Batch operation functions moved to batch_ops.go (bd-c796)
|
||||
// Batch operation functions moved to batch_ops.go
|
||||
|
||||
// GetIssue retrieves an issue by ID
|
||||
func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue, error) {
|
||||
@@ -260,19 +260,19 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
|
||||
var deletedBy sql.NullString
|
||||
var deleteReason sql.NullString
|
||||
var originalType sql.NullString
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
var sender sql.NullString
|
||||
var wisp sql.NullInt64
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
var pinned sql.NullInt64
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
var timeoutNs sql.NullInt64
|
||||
var waiters sql.NullString
|
||||
// Agent fields (gt-h5sza)
|
||||
// Agent fields
|
||||
var hookBead sql.NullString
|
||||
var roleBead sql.NullString
|
||||
var agentState sql.NullString
|
||||
@@ -353,22 +353,22 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
|
||||
if originalType.Valid {
|
||||
issue.OriginalType = originalType.String
|
||||
}
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
if sender.Valid {
|
||||
issue.Sender = sender.String
|
||||
}
|
||||
if wisp.Valid && wisp.Int64 != 0 {
|
||||
issue.Ephemeral = true
|
||||
}
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
if pinned.Valid && pinned.Int64 != 0 {
|
||||
issue.Pinned = true
|
||||
}
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
}
|
||||
@@ -381,7 +381,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
|
||||
if waiters.Valid && waiters.String != "" {
|
||||
issue.Waiters = parseJSONStringArray(waiters.String)
|
||||
}
|
||||
// Agent fields (gt-h5sza)
|
||||
// Agent fields
|
||||
if hookBead.Valid {
|
||||
issue.HookBead = hookBead.String
|
||||
}
|
||||
@@ -503,14 +503,14 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
|
||||
var deletedBy sql.NullString
|
||||
var deleteReason sql.NullString
|
||||
var originalType sql.NullString
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
var sender sql.NullString
|
||||
var wisp sql.NullInt64
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
var pinned sql.NullInt64
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
var timeoutNs sql.NullInt64
|
||||
@@ -585,22 +585,22 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
|
||||
if originalType.Valid {
|
||||
issue.OriginalType = originalType.String
|
||||
}
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
if sender.Valid {
|
||||
issue.Sender = sender.String
|
||||
}
|
||||
if wisp.Valid && wisp.Int64 != 0 {
|
||||
issue.Ephemeral = true
|
||||
}
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
if pinned.Valid && pinned.Int64 != 0 {
|
||||
issue.Pinned = true
|
||||
}
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
}
|
||||
@@ -638,14 +638,14 @@ var allowedUpdateFields = map[string]bool{
|
||||
"estimated_minutes": true,
|
||||
"external_ref": true,
|
||||
"closed_at": true,
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
"sender": true,
|
||||
"wisp": true, // Database column is 'ephemeral', mapped in UpdateIssue
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
"pinned": true,
|
||||
// NOTE: replies_to, relates_to, duplicate_of, superseded_by removed per Decision 004
|
||||
// Use AddDependency() to create graph edges instead
|
||||
// Agent slot fields (gt-h5sza)
|
||||
// Agent slot fields
|
||||
"hook_bead": true,
|
||||
"role_bead": true,
|
||||
"agent_state": true,
|
||||
@@ -655,7 +655,7 @@ var allowedUpdateFields = map[string]bool{
|
||||
}
|
||||
|
||||
// validatePriority validates a priority value
|
||||
// Validation functions moved to validators.go (bd-d9e0)
|
||||
// Validation functions moved to validators.go
|
||||
|
||||
// determineEventType determines the event type for an update based on old and new status
|
||||
func determineEventType(oldIssue *types.Issue, updates map[string]interface{}) types.EventType {
|
||||
@@ -734,7 +734,7 @@ func (s *SQLiteStorage) UpdateIssue(ctx context.Context, id string, updates map[
|
||||
return fmt.Errorf("issue %s not found", id)
|
||||
}
|
||||
|
||||
// Fetch custom statuses for validation (bd-1pj6)
|
||||
// Fetch custom statuses for validation
|
||||
customStatuses, err := s.GetCustomStatuses(ctx)
|
||||
if err != nil {
|
||||
return wrapDBError("get custom statuses", err)
|
||||
@@ -767,7 +767,7 @@ func (s *SQLiteStorage) UpdateIssue(ctx context.Context, id string, updates map[
|
||||
// Auto-manage closed_at when status changes (enforce invariant)
|
||||
setClauses, args = manageClosedAt(oldIssue, updates, setClauses, args)
|
||||
|
||||
// Recompute content_hash if any content fields changed (bd-95)
|
||||
// Recompute content_hash if any content fields changed
|
||||
contentChanged := false
|
||||
contentFields := []string{"title", "description", "design", "acceptance_criteria", "notes", "status", "priority", "issue_type", "assignee", "external_ref"}
|
||||
for _, field := range contentFields {
|
||||
@@ -886,7 +886,7 @@ func (s *SQLiteStorage) UpdateIssue(ctx context.Context, id string, updates map[
|
||||
return fmt.Errorf("failed to mark issue dirty: %w", err)
|
||||
}
|
||||
|
||||
// Invalidate blocked issues cache if status changed (bd-5qim)
|
||||
// Invalidate blocked issues cache if status changed
|
||||
// Status changes affect which issues are blocked (blockers must be open/in_progress/blocked)
|
||||
if _, statusChanged := updates["status"]; statusChanged {
|
||||
if err := s.invalidateBlockedCache(ctx, tx); err != nil {
|
||||
@@ -1023,14 +1023,14 @@ func (s *SQLiteStorage) RenameDependencyPrefix(ctx context.Context, oldPrefix, n
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenameCounterPrefix is a no-op with hash-based IDs (bd-8e05)
|
||||
// RenameCounterPrefix is a no-op with hash-based IDs
|
||||
// Kept for backward compatibility with rename-prefix command
|
||||
func (s *SQLiteStorage) RenameCounterPrefix(ctx context.Context, oldPrefix, newPrefix string) error {
|
||||
// Hash-based IDs don't use counters, so nothing to update
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetCounter is a no-op with hash-based IDs (bd-8e05)
|
||||
// ResetCounter is a no-op with hash-based IDs
|
||||
// Kept for backward compatibility
|
||||
func (s *SQLiteStorage) ResetCounter(ctx context.Context, prefix string) error {
|
||||
// Hash-based IDs don't use counters, so nothing to reset
|
||||
@@ -1086,7 +1086,7 @@ func (s *SQLiteStorage) CloseIssue(ctx context.Context, id string, reason string
|
||||
return fmt.Errorf("failed to mark issue dirty: %w", err)
|
||||
}
|
||||
|
||||
// Invalidate blocked issues cache since status changed to closed (bd-5qim)
|
||||
// Invalidate blocked issues cache since status changed to closed
|
||||
// Closed issues don't block others, so this affects blocking calculations
|
||||
if err := s.invalidateBlockedCache(ctx, tx); err != nil {
|
||||
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
|
||||
@@ -1155,7 +1155,7 @@ func (s *SQLiteStorage) CreateTombstone(ctx context.Context, id string, actor st
|
||||
return fmt.Errorf("failed to mark issue dirty: %w", err)
|
||||
}
|
||||
|
||||
// Invalidate blocked issues cache since status changed (bd-5qim)
|
||||
// Invalidate blocked issues cache since status changed
|
||||
// Tombstone issues don't block others, so this affects blocking calculations
|
||||
if err := s.invalidateBlockedCache(ctx, tx); err != nil {
|
||||
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
|
||||
@@ -1188,7 +1188,7 @@ func (s *SQLiteStorage) DeleteIssue(ctx context.Context, id string) error {
|
||||
return fmt.Errorf("failed to delete events: %w", err)
|
||||
}
|
||||
|
||||
// Delete comments (no FK cascade on this table) (bd-687g)
|
||||
// Delete comments (no FK cascade on this table)
|
||||
_, err = tx.ExecContext(ctx, `DELETE FROM comments WHERE issue_id = ?`, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete comments: %w", err)
|
||||
@@ -1218,7 +1218,7 @@ func (s *SQLiteStorage) DeleteIssue(ctx context.Context, id string) error {
|
||||
return wrapDBError("commit delete transaction", err)
|
||||
}
|
||||
|
||||
// REMOVED (bd-c7af): Counter sync after deletion - no longer needed with hash IDs
|
||||
// REMOVED: Counter sync after deletion - no longer needed with hash IDs
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1272,7 +1272,7 @@ func (s *SQLiteStorage) DeleteIssues(ctx context.Context, ids []string, cascade
|
||||
return nil, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
// REMOVED (bd-c7af): Counter sync after deletion - no longer needed with hash IDs
|
||||
// REMOVED: Counter sync after deletion - no longer needed with hash IDs
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -1424,7 +1424,7 @@ func (s *SQLiteStorage) populateDeleteStats(ctx context.Context, tx *sql.Tx, inC
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) executeDelete(ctx context.Context, tx *sql.Tx, inClause string, args []interface{}, result *DeleteIssuesResult) error {
|
||||
// Note: This method now creates tombstones instead of hard-deleting (bd-3b4)
|
||||
// Note: This method now creates tombstones instead of hard-deleting
|
||||
// Only dependencies are deleted - issues are converted to tombstones
|
||||
|
||||
// 1. Delete dependencies - tombstones don't block other issues
|
||||
@@ -1500,7 +1500,7 @@ func (s *SQLiteStorage) executeDelete(ctx context.Context, tx *sql.Tx, inClause
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Invalidate blocked issues cache since statuses changed (bd-5qim)
|
||||
// 4. Invalidate blocked issues cache since statuses changed
|
||||
if err := s.invalidateBlockedCache(ctx, tx); err != nil {
|
||||
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
|
||||
}
|
||||
@@ -1591,7 +1591,7 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
|
||||
whereClauses = append(whereClauses, "status = ?")
|
||||
args = append(args, *filter.Status)
|
||||
} else if !filter.IncludeTombstones {
|
||||
// Exclude tombstones by default unless explicitly filtering for them (bd-1bu)
|
||||
// Exclude tombstones by default unless explicitly filtering for them
|
||||
whereClauses = append(whereClauses, "status != ?")
|
||||
args = append(args, types.StatusTombstone)
|
||||
}
|
||||
@@ -1686,7 +1686,7 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
|
||||
whereClauses = append(whereClauses, fmt.Sprintf("id IN (%s)", strings.Join(placeholders, ", ")))
|
||||
}
|
||||
|
||||
// Wisp filtering (bd-kwro.9)
|
||||
// Wisp filtering
|
||||
if filter.Ephemeral != nil {
|
||||
if *filter.Ephemeral {
|
||||
whereClauses = append(whereClauses, "ephemeral = 1") // SQL column is still 'ephemeral'
|
||||
@@ -1695,7 +1695,7 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
|
||||
}
|
||||
}
|
||||
|
||||
// Pinned filtering (bd-7h5)
|
||||
// Pinned filtering
|
||||
if filter.Pinned != nil {
|
||||
if *filter.Pinned {
|
||||
whereClauses = append(whereClauses, "pinned = 1")
|
||||
@@ -1704,7 +1704,7 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
|
||||
}
|
||||
}
|
||||
|
||||
// Template filtering (beads-1ra)
|
||||
// Template filtering
|
||||
if filter.IsTemplate != nil {
|
||||
if *filter.IsTemplate {
|
||||
whereClauses = append(whereClauses, "is_template = 1")
|
||||
@@ -1713,7 +1713,7 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
|
||||
}
|
||||
}
|
||||
|
||||
// Parent filtering (bd-yqhh): filter children by parent issue
|
||||
// Parent filtering: filter children by parent issue
|
||||
if filter.ParentID != nil {
|
||||
whereClauses = append(whereClauses, "id IN (SELECT issue_id FROM dependencies WHERE type = 'parent-child' AND depends_on_id = ?)")
|
||||
args = append(args, *filter.ParentID)
|
||||
|
||||
@@ -13,16 +13,16 @@ import (
|
||||
|
||||
// GetReadyWork returns issues with no open blockers
|
||||
// By default, shows both 'open' and 'in_progress' issues so epics/tasks
|
||||
// ready to close are visible (bd-165)
|
||||
// Excludes pinned issues which are persistent anchors, not actionable work (bd-92u)
|
||||
// ready to close are visible.
|
||||
// Excludes pinned issues which are persistent anchors, not actionable work.
|
||||
func (s *SQLiteStorage) GetReadyWork(ctx context.Context, filter types.WorkFilter) ([]*types.Issue, error) {
|
||||
whereClauses := []string{
|
||||
"i.pinned = 0", // Exclude pinned issues (bd-92u)
|
||||
"(i.ephemeral = 0 OR i.ephemeral IS NULL)", // Exclude wisps (hq-t15s)
|
||||
"i.pinned = 0", // Exclude pinned issues
|
||||
"(i.ephemeral = 0 OR i.ephemeral IS NULL)", // Exclude wisps
|
||||
}
|
||||
args := []interface{}{}
|
||||
|
||||
// Default to open OR in_progress if not specified (bd-165)
|
||||
// Default to open OR in_progress if not specified
|
||||
if filter.Status == "" {
|
||||
whereClauses = append(whereClauses, "i.status IN ('open', 'in_progress')")
|
||||
} else {
|
||||
@@ -30,12 +30,12 @@ func (s *SQLiteStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte
|
||||
args = append(args, filter.Status)
|
||||
}
|
||||
|
||||
// Filter by issue type (gt-ktf3: MQ integration)
|
||||
// Filter by issue type for MQ integration
|
||||
if filter.Type != "" {
|
||||
whereClauses = append(whereClauses, "i.issue_type = ?")
|
||||
args = append(args, filter.Type)
|
||||
} else {
|
||||
// Exclude workflow types from ready work by default (gt-7xtn)
|
||||
// Exclude workflow types from ready work by default
|
||||
// These are internal workflow items, not work for polecats to claim:
|
||||
// - merge-request: processed by Refinery
|
||||
// - gate: async wait conditions
|
||||
@@ -123,7 +123,7 @@ func (s *SQLiteStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte
|
||||
}
|
||||
orderBySQL := buildOrderByClause(sortPolicy)
|
||||
|
||||
// Use blocked_issues_cache for performance (bd-5qim)
|
||||
// Use blocked_issues_cache for performance
|
||||
// This optimization replaces the recursive CTE that computed blocked issues on every query.
|
||||
// Performance improvement: 752ms → 29ms on 10K issues (25x speedup).
|
||||
//
|
||||
@@ -162,7 +162,7 @@ func (s *SQLiteStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Filter out issues with unsatisfied external dependencies (bd-zmmy)
|
||||
// Filter out issues with unsatisfied external dependencies
|
||||
// Only check if external_projects are configured
|
||||
if len(config.GetExternalProjects()) > 0 && len(issues) > 0 {
|
||||
issues, err = s.filterByExternalDeps(ctx, issues)
|
||||
@@ -324,14 +324,14 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi
|
||||
var deletedBy sql.NullString
|
||||
var deleteReason sql.NullString
|
||||
var originalType sql.NullString
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
var sender sql.NullString
|
||||
var ephemeral sql.NullInt64
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
var pinned sql.NullInt64
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
var timeoutNs sql.NullInt64
|
||||
@@ -395,22 +395,22 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi
|
||||
if originalType.Valid {
|
||||
issue.OriginalType = originalType.String
|
||||
}
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
if sender.Valid {
|
||||
issue.Sender = sender.String
|
||||
}
|
||||
if ephemeral.Valid && ephemeral.Int64 != 0 {
|
||||
issue.Ephemeral = true
|
||||
}
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
if pinned.Valid && pinned.Int64 != 0 {
|
||||
issue.Pinned = true
|
||||
}
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
}
|
||||
@@ -431,18 +431,18 @@ 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)
|
||||
// Note: Pinned issues are excluded from the output.
|
||||
// Note: Includes external: references in blocked_by list.
|
||||
func (s *SQLiteStorage) GetBlockedIssues(ctx context.Context, filter types.WorkFilter) ([]*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)
|
||||
// Exclude pinned issues.
|
||||
//
|
||||
// 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)
|
||||
// - External refs are always considered "open" until resolved
|
||||
|
||||
// Build additional WHERE clauses for filtering
|
||||
var filterClauses []string
|
||||
@@ -572,7 +572,7 @@ func (s *SQLiteStorage) GetBlockedIssues(ctx context.Context, filter types.WorkF
|
||||
blocked = append(blocked, &issue)
|
||||
}
|
||||
|
||||
// Filter out satisfied external dependencies from BlockedBy lists (bd-396j)
|
||||
// Filter out satisfied external dependencies from BlockedBy lists
|
||||
// Only check if external_projects are configured
|
||||
if len(config.GetExternalProjects()) > 0 && len(blocked) > 0 {
|
||||
blocked = filterBlockedByExternalDeps(ctx, blocked)
|
||||
|
||||
@@ -120,7 +120,7 @@ func NewWithTimeout(ctx context.Context, path string, busyTimeout time.Duration)
|
||||
|
||||
// For all in-memory databases (including file::memory:), force single connection.
|
||||
// SQLite's in-memory databases are isolated per connection by default.
|
||||
// Without this, different connections in the pool can't see each other's writes (bd-b121, bd-yvlc).
|
||||
// Without this, different connections in the pool can't see each other's writes.
|
||||
isInMemory := path == ":memory:" ||
|
||||
(strings.HasPrefix(path, "file:") && strings.Contains(path, "mode=memory"))
|
||||
if isInMemory {
|
||||
@@ -130,7 +130,7 @@ func NewWithTimeout(ctx context.Context, path string, busyTimeout time.Duration)
|
||||
// For file-based databases in daemon mode, limit connection pool to prevent
|
||||
// connection exhaustion under concurrent load. SQLite WAL mode supports
|
||||
// 1 writer + unlimited readers, but we limit to prevent goroutine pile-up
|
||||
// on write lock contention (bd-qhws).
|
||||
// on write lock contention.
|
||||
maxConns := runtime.NumCPU() + 1 // 1 writer + N readers
|
||||
db.SetMaxOpenConns(maxConns)
|
||||
db.SetMaxIdleConns(2)
|
||||
@@ -159,7 +159,7 @@ func NewWithTimeout(ctx context.Context, path string, busyTimeout time.Duration)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify schema compatibility after migrations (bd-ckvw)
|
||||
// Verify schema compatibility after migrations
|
||||
// First attempt
|
||||
if err := verifySchemaCompatibility(db); err != nil {
|
||||
// Schema probe failed - retry migrations once
|
||||
@@ -191,7 +191,7 @@ func NewWithTimeout(ctx context.Context, path string, busyTimeout time.Duration)
|
||||
busyTimeout: busyTimeout,
|
||||
}
|
||||
|
||||
// Hydrate from multi-repo config if configured (bd-307)
|
||||
// Hydrate from multi-repo config if configured
|
||||
// Skip for in-memory databases (used in tests)
|
||||
if path != ":memory:" {
|
||||
_, err := storage.HydrateFromMultiRepo(ctx)
|
||||
|
||||
@@ -48,7 +48,7 @@ func (s *SQLiteStorage) RunInTransaction(ctx context.Context, fn func(tx storage
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
// Start IMMEDIATE transaction to acquire write lock early.
|
||||
// Use retry logic with exponential backoff to handle SQLITE_BUSY (bd-ola6)
|
||||
// Use retry logic with exponential backoff to handle SQLITE_BUSY
|
||||
if err := beginImmediateWithRetry(ctx, conn, 5, 10*time.Millisecond); err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func (s *SQLiteStorage) RunInTransaction(ctx context.Context, fn func(tx storage
|
||||
|
||||
// CreateIssue creates a new issue within the transaction.
|
||||
func (t *sqliteTxStorage) CreateIssue(ctx context.Context, issue *types.Issue, actor string) error {
|
||||
// Fetch custom statuses for validation (bd-1pj6)
|
||||
// Fetch custom statuses for validation
|
||||
customStatuses, err := t.GetCustomStatuses(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get custom statuses: %w", err)
|
||||
@@ -132,7 +132,7 @@ func (t *sqliteTxStorage) CreateIssue(ctx context.Context, issue *types.Issue, a
|
||||
return fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Compute content hash (bd-95)
|
||||
// Compute content hash
|
||||
if issue.ContentHash == "" {
|
||||
issue.ContentHash = issue.ComputeContentHash()
|
||||
}
|
||||
@@ -141,13 +141,13 @@ func (t *sqliteTxStorage) CreateIssue(ctx context.Context, issue *types.Issue, a
|
||||
var configPrefix string
|
||||
err = t.conn.QueryRowContext(ctx, `SELECT value FROM config WHERE key = ?`, "issue_prefix").Scan(&configPrefix)
|
||||
if err == sql.ErrNoRows || configPrefix == "" {
|
||||
// CRITICAL: Reject operation if issue_prefix config is missing (bd-166)
|
||||
// CRITICAL: Reject operation if issue_prefix config is missing
|
||||
return fmt.Errorf("database not initialized: issue_prefix config is missing (run 'bd init --prefix <prefix>' first)")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to get config: %w", err)
|
||||
}
|
||||
|
||||
// Use IDPrefix override if set, combined with config prefix (bd-hobo)
|
||||
// Use IDPrefix override if set, combined with config prefix
|
||||
// e.g., configPrefix="bd" + IDPrefix="wisp" → "bd-wisp"
|
||||
prefix := configPrefix
|
||||
if issue.IDPrefix != "" {
|
||||
@@ -156,14 +156,14 @@ func (t *sqliteTxStorage) CreateIssue(ctx context.Context, issue *types.Issue, a
|
||||
|
||||
// Generate or validate ID
|
||||
if issue.ID == "" {
|
||||
// Generate hash-based ID with adaptive length based on database size (bd-ea2a13)
|
||||
// Generate hash-based ID with adaptive length based on database size
|
||||
generatedID, err := GenerateIssueID(ctx, t.conn, prefix, issue, actor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate issue ID: %w", err)
|
||||
}
|
||||
issue.ID = generatedID
|
||||
} else {
|
||||
// Validate that explicitly provided ID matches the configured prefix (bd-177)
|
||||
// Validate that explicitly provided ID matches the configured prefix
|
||||
if err := ValidateIssueIDPrefix(issue.ID, prefix); err != nil {
|
||||
return fmt.Errorf("failed to validate issue ID prefix: %w", err)
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func (t *sqliteTxStorage) CreateIssues(ctx context.Context, issues []*types.Issu
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch custom statuses for validation (bd-1pj6)
|
||||
// Fetch custom statuses for validation
|
||||
customStatuses, err := t.GetCustomStatuses(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get custom statuses: %w", err)
|
||||
@@ -370,7 +370,7 @@ func (t *sqliteTxStorage) UpdateIssue(ctx context.Context, id string, updates ma
|
||||
return fmt.Errorf("issue %s not found", id)
|
||||
}
|
||||
|
||||
// Fetch custom statuses for validation (bd-1pj6)
|
||||
// Fetch custom statuses for validation
|
||||
customStatuses, err := t.GetCustomStatuses(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get custom statuses: %w", err)
|
||||
@@ -398,7 +398,7 @@ func (t *sqliteTxStorage) UpdateIssue(ctx context.Context, id string, updates ma
|
||||
// Auto-manage closed_at when status changes
|
||||
setClauses, args = manageClosedAt(oldIssue, updates, setClauses, args)
|
||||
|
||||
// Recompute content_hash if any content fields changed (bd-95)
|
||||
// Recompute content_hash if any content fields changed
|
||||
contentChanged := false
|
||||
contentFields := []string{"title", "description", "design", "acceptance_criteria", "notes", "status", "priority", "issue_type", "assignee", "external_ref"}
|
||||
for _, field := range contentFields {
|
||||
@@ -449,7 +449,7 @@ func (t *sqliteTxStorage) UpdateIssue(ctx context.Context, id string, updates ma
|
||||
return fmt.Errorf("failed to mark issue dirty: %w", err)
|
||||
}
|
||||
|
||||
// Invalidate blocked issues cache if status changed (bd-1c4h)
|
||||
// Invalidate blocked issues cache if status changed
|
||||
// Status changes affect which issues are blocked (blockers must be open/in_progress/blocked)
|
||||
if _, statusChanged := updates["status"]; statusChanged {
|
||||
if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil {
|
||||
@@ -555,7 +555,7 @@ func (t *sqliteTxStorage) CloseIssue(ctx context.Context, id string, reason stri
|
||||
return fmt.Errorf("failed to mark issue dirty: %w", err)
|
||||
}
|
||||
|
||||
// Invalidate blocked issues cache since status changed to closed (bd-1c4h)
|
||||
// Invalidate blocked issues cache since status changed to closed
|
||||
// Closed issues don't block others, so this affects blocking calculations
|
||||
if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil {
|
||||
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
|
||||
@@ -617,7 +617,7 @@ func (t *sqliteTxStorage) AddDependency(ctx context.Context, dep *types.Dependen
|
||||
return fmt.Errorf("issue %s not found", dep.IssueID)
|
||||
}
|
||||
|
||||
// External refs (external:<project>:<capability>) don't need target validation (bd-zmmy)
|
||||
// External refs (external:<project>:<capability>) don't need target validation
|
||||
// They are resolved lazily at query time by CheckExternalDep
|
||||
isExternalRef := strings.HasPrefix(dep.DependsOnID, "external:")
|
||||
|
||||
@@ -720,7 +720,7 @@ func (t *sqliteTxStorage) AddDependency(ctx context.Context, dep *types.Dependen
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate blocked cache for blocking dependencies (bd-1c4h, bd-kzda)
|
||||
// Invalidate blocked cache for blocking dependencies
|
||||
if dep.Type.AffectsReadyWork() {
|
||||
if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil {
|
||||
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
|
||||
@@ -732,13 +732,13 @@ func (t *sqliteTxStorage) AddDependency(ctx context.Context, dep *types.Dependen
|
||||
|
||||
// RemoveDependency removes a dependency within the transaction.
|
||||
func (t *sqliteTxStorage) RemoveDependency(ctx context.Context, issueID, dependsOnID string, actor string) error {
|
||||
// First, check what type of dependency is being removed (bd-1c4h)
|
||||
// First, check what type of dependency is being removed
|
||||
var depType types.DependencyType
|
||||
err := t.conn.QueryRowContext(ctx, `
|
||||
SELECT type FROM dependencies WHERE issue_id = ? AND depends_on_id = ?
|
||||
`, issueID, dependsOnID).Scan(&depType)
|
||||
|
||||
// Store whether cache needs invalidation before deletion (bd-1c4h, bd-kzda)
|
||||
// Store whether cache needs invalidation before deletion
|
||||
needsCacheInvalidation := false
|
||||
if err == nil {
|
||||
needsCacheInvalidation = depType.AffectsReadyWork()
|
||||
@@ -777,7 +777,7 @@ func (t *sqliteTxStorage) RemoveDependency(ctx context.Context, issueID, depends
|
||||
return fmt.Errorf("failed to mark depends-on issue dirty: %w", err)
|
||||
}
|
||||
|
||||
// Invalidate blocked cache if this was a blocking dependency (bd-1c4h)
|
||||
// Invalidate blocked cache if this was a blocking dependency
|
||||
if needsCacheInvalidation {
|
||||
if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil {
|
||||
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
|
||||
@@ -993,7 +993,7 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter
|
||||
whereClauses = append(whereClauses, "status = ?")
|
||||
args = append(args, *filter.Status)
|
||||
} else if !filter.IncludeTombstones {
|
||||
// Exclude tombstones by default unless explicitly filtering for them (bd-1bu)
|
||||
// Exclude tombstones by default unless explicitly filtering for them
|
||||
whereClauses = append(whereClauses, "status != ?")
|
||||
args = append(args, types.StatusTombstone)
|
||||
}
|
||||
@@ -1088,7 +1088,7 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter
|
||||
whereClauses = append(whereClauses, fmt.Sprintf("id IN (%s)", strings.Join(placeholders, ", ")))
|
||||
}
|
||||
|
||||
// Wisp filtering (bd-kwro.9)
|
||||
// Wisp filtering
|
||||
if filter.Ephemeral != nil {
|
||||
if *filter.Ephemeral {
|
||||
whereClauses = append(whereClauses, "ephemeral = 1") // SQL column is still 'ephemeral'
|
||||
@@ -1097,7 +1097,7 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter
|
||||
}
|
||||
}
|
||||
|
||||
// Pinned filtering (bd-7h5)
|
||||
// Pinned filtering
|
||||
if filter.Pinned != nil {
|
||||
if *filter.Pinned {
|
||||
whereClauses = append(whereClauses, "pinned = 1")
|
||||
@@ -1106,7 +1106,7 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter
|
||||
}
|
||||
}
|
||||
|
||||
// Parent filtering (bd-yqhh): filter children by parent issue
|
||||
// Parent filtering: filter children by parent issue
|
||||
if filter.ParentID != nil {
|
||||
whereClauses = append(whereClauses, "id IN (SELECT issue_id FROM dependencies WHERE type = 'parent-child' AND depends_on_id = ?)")
|
||||
args = append(args, *filter.ParentID)
|
||||
@@ -1171,14 +1171,14 @@ func scanIssueRow(row scanner) (*types.Issue, error) {
|
||||
var deletedBy sql.NullString
|
||||
var deleteReason sql.NullString
|
||||
var originalType sql.NullString
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
var sender sql.NullString
|
||||
var wisp sql.NullInt64
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
var pinned sql.NullInt64
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
var timeoutNs sql.NullInt64
|
||||
@@ -1239,22 +1239,22 @@ func scanIssueRow(row scanner) (*types.Issue, error) {
|
||||
if originalType.Valid {
|
||||
issue.OriginalType = originalType.String
|
||||
}
|
||||
// Messaging fields (bd-kwro)
|
||||
// Messaging fields
|
||||
if sender.Valid {
|
||||
issue.Sender = sender.String
|
||||
}
|
||||
if wisp.Valid && wisp.Int64 != 0 {
|
||||
issue.Ephemeral = true
|
||||
}
|
||||
// Pinned field (bd-7h5)
|
||||
// Pinned field
|
||||
if pinned.Valid && pinned.Int64 != 0 {
|
||||
issue.Pinned = true
|
||||
}
|
||||
// Template field (beads-1ra)
|
||||
// Template field
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Gate fields (bd-udsi)
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user