feat: add gate issue type and CLI commands for async coordination (bd-udsi)

Add async gates - coordination primitives for agents to wait on external events
like CI completion, PR merges, timers, or human approval.

Changes:
- Add 'gate' issue type to types.go with gate-specific fields:
  - AwaitType: condition type (gh:run, gh:pr, timer, human, mail)
  - AwaitID: condition identifier
  - Timeout: max wait duration
  - Waiters: mail addresses to notify when gate clears
- Add SQLite migration 027_gate_columns for new fields
- Update all SQLite storage queries to handle gate fields
- Add bd gate commands: create, show, list, close, wait
- All commands support --json output and --no-daemon mode

Closes: bd-2v0f, bd-lz49, bd-u66e

🤖 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-23 12:06:42 -08:00
parent cfd412b2d7
commit 47b86b35d8
18 changed files with 756 additions and 24 deletions

View File

@@ -59,6 +59,12 @@ type Issue struct {
// HOP fields (bd-7pwh): entity tracking for CV chains
Creator *EntityRef `json:"creator,omitempty"` // Who created this issue (human, agent, or org)
Validations []Validation `json:"validations,omitempty"` // Who validated/approved this work
// Gate fields (bd-udsi): async coordination primitives
AwaitType string `json:"await_type,omitempty"` // Condition type: gh:run, gh:pr, timer, human, mail
AwaitID string `json:"await_id,omitempty"` // Condition identifier (e.g., run ID, PR number)
Timeout time.Duration `json:"timeout,omitempty"` // Max wait time before escalation
Waiters []string `json:"waiters,omitempty"` // Mail addresses to notify when gate clears
}
// ComputeContentHash creates a deterministic hash of the issue's content.
@@ -140,6 +146,17 @@ func (i *Issue) ComputeContentHash() string {
}
h.Write([]byte{0})
}
// Hash gate fields for async coordination (bd-udsi)
h.Write([]byte(i.AwaitType))
h.Write([]byte{0})
h.Write([]byte(i.AwaitID))
h.Write([]byte{0})
h.Write([]byte(fmt.Sprintf("%d", i.Timeout)))
h.Write([]byte{0})
for _, waiter := range i.Waiters {
h.Write([]byte(waiter))
h.Write([]byte{0})
}
return fmt.Sprintf("%x", h.Sum(nil))
}
@@ -313,12 +330,13 @@ const (
TypeMessage IssueType = "message" // Ephemeral communication between workers
TypeMergeRequest IssueType = "merge-request" // Merge queue entry for refinery processing
TypeMolecule IssueType = "molecule" // Template molecule for issue hierarchies (beads-1ra)
TypeGate IssueType = "gate" // Async coordination gate (bd-udsi)
)
// IsValid checks if the issue type value is valid
func (t IssueType) IsValid() bool {
switch t {
case TypeBug, TypeFeature, TypeTask, TypeEpic, TypeChore, TypeMessage, TypeMergeRequest, TypeMolecule:
case TypeBug, TypeFeature, TypeTask, TypeEpic, TypeChore, TypeMessage, TypeMergeRequest, TypeMolecule, TypeGate:
return true
}
return false