feat(bd-1pj6): Add custom status states via config
Users can now define custom status states for multi-step pipelines using: bd config set status.custom "awaiting_review,awaiting_testing,awaiting_docs" Changes: - Add Status.IsValidWithCustom() method for custom status validation - Add Issue.ValidateWithCustomStatuses() method - Add GetCustomStatuses() method to storage interface - Update CreateIssue/UpdateIssue to support custom statuses - Add comprehensive tests for custom status functionality - Update config command help text with custom status documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -91,8 +91,14 @@ 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 {
|
||||
// Validate issue before creating
|
||||
if err := issue.Validate(); err != nil {
|
||||
// Fetch custom statuses for validation (bd-1pj6)
|
||||
customStatuses, err := t.GetCustomStatuses(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get custom statuses: %w", err)
|
||||
}
|
||||
|
||||
// Validate issue before creating (with custom status support)
|
||||
if err := issue.ValidateWithCustomStatuses(customStatuses); err != nil {
|
||||
return fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -108,7 +114,7 @@ func (t *sqliteTxStorage) CreateIssue(ctx context.Context, issue *types.Issue, a
|
||||
|
||||
// Get prefix from config (needed for both ID generation and validation)
|
||||
var prefix string
|
||||
err := t.conn.QueryRowContext(ctx, `SELECT value FROM config WHERE key = ?`, "issue_prefix").Scan(&prefix)
|
||||
err = t.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)
|
||||
return fmt.Errorf("database not initialized: issue_prefix config is missing (run 'bd init --prefix <prefix>' first)")
|
||||
@@ -170,10 +176,16 @@ func (t *sqliteTxStorage) CreateIssues(ctx context.Context, issues []*types.Issu
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate and prepare all issues first
|
||||
// Fetch custom statuses for validation (bd-1pj6)
|
||||
customStatuses, err := t.GetCustomStatuses(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get custom statuses: %w", err)
|
||||
}
|
||||
|
||||
// Validate and prepare all issues first (with custom status support)
|
||||
now := time.Now()
|
||||
for _, issue := range issues {
|
||||
if err := issue.Validate(); err != nil {
|
||||
if err := issue.ValidateWithCustomStatuses(customStatuses); err != nil {
|
||||
return fmt.Errorf("validation failed for issue: %w", err)
|
||||
}
|
||||
issue.CreatedAt = now
|
||||
@@ -185,7 +197,7 @@ func (t *sqliteTxStorage) CreateIssues(ctx context.Context, issues []*types.Issu
|
||||
|
||||
// Get prefix from config
|
||||
var prefix string
|
||||
err := t.conn.QueryRowContext(ctx, `SELECT value FROM config WHERE key = ?`, "issue_prefix").Scan(&prefix)
|
||||
err = t.conn.QueryRowContext(ctx, `SELECT value FROM config WHERE key = ?`, "issue_prefix").Scan(&prefix)
|
||||
if err == sql.ErrNoRows || prefix == "" {
|
||||
return fmt.Errorf("database not initialized: issue_prefix config is missing")
|
||||
} else if err != nil {
|
||||
@@ -297,6 +309,12 @@ 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)
|
||||
customStatuses, err := t.GetCustomStatuses(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get custom statuses: %w", err)
|
||||
}
|
||||
|
||||
// Build update query with validated field names
|
||||
setClauses := []string{"updated_at = ?"}
|
||||
args := []interface{}{time.Now()}
|
||||
@@ -307,8 +325,8 @@ func (t *sqliteTxStorage) UpdateIssue(ctx context.Context, id string, updates ma
|
||||
return fmt.Errorf("invalid field for update: %s", key)
|
||||
}
|
||||
|
||||
// Validate field values
|
||||
if err := validateFieldUpdate(key, value); err != nil {
|
||||
// Validate field values (with custom status support)
|
||||
if err := validateFieldUpdateWithCustomStatuses(key, value, customStatuses); err != nil {
|
||||
return fmt.Errorf("failed to validate field update: %w", err)
|
||||
}
|
||||
|
||||
@@ -791,6 +809,18 @@ func (t *sqliteTxStorage) GetConfig(ctx context.Context, key string) (string, er
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// GetCustomStatuses retrieves the list of custom status states from config within the transaction.
|
||||
func (t *sqliteTxStorage) GetCustomStatuses(ctx context.Context) ([]string, error) {
|
||||
value, err := t.GetConfig(ctx, CustomStatusConfigKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return parseCustomStatuses(value), nil
|
||||
}
|
||||
|
||||
// SetMetadata sets a metadata value within the transaction.
|
||||
func (t *sqliteTxStorage) SetMetadata(ctx context.Context, key, value string) error {
|
||||
_, err := t.conn.ExecContext(ctx, `
|
||||
|
||||
Reference in New Issue
Block a user