feat: add waits-for dependency type for fanout gates (bd-xo1o.2)

Adds 'waits-for' dependency type for dynamic molecule bonding:
- DepWaitsFor blocks an issue until spawner's children are closed
- Two gate types: all-children (wait for all) or any-children (first)
- Updated blocked_cache.go CTE to handle waits-for dependencies
- Added --waits-for and --waits-for-gate flags to bd create command
- Added WaitsForMeta struct for gate metadata storage
- Full test coverage for all gate types and dynamic child scenarios

This enables patrol molecules to wait for dynamically-bonded arms to
complete before proceeding (Christmas Ornament pattern).

🤖 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 03:59:43 -08:00
parent 37fd1ce614
commit fa9a88e2a3
7 changed files with 328 additions and 5 deletions

View File

@@ -368,6 +368,7 @@ const (
DepBlocks DependencyType = "blocks"
DepParentChild DependencyType = "parent-child"
DepConditionalBlocks DependencyType = "conditional-blocks" // B runs only if A fails (bd-kzda)
DepWaitsFor DependencyType = "waits-for" // Fanout gate: wait for dynamic children (bd-xo1o.2)
// Association types
DepRelated DependencyType = "related"
@@ -396,7 +397,7 @@ func (d DependencyType) IsValid() bool {
// Returns false for custom/user-defined types (which are still valid).
func (d DependencyType) IsWellKnown() bool {
switch d {
case DepBlocks, DepParentChild, DepConditionalBlocks, DepRelated, DepDiscoveredFrom,
case DepBlocks, DepParentChild, DepConditionalBlocks, DepWaitsFor, DepRelated, DepDiscoveredFrom,
DepRepliesTo, DepRelatesTo, DepDuplicates, DepSupersedes,
DepAuthoredBy, DepAssignedTo, DepApprovedBy:
return true
@@ -405,11 +406,27 @@ func (d DependencyType) IsWellKnown() bool {
}
// AffectsReadyWork returns true if this dependency type blocks work.
// Only "blocks", "parent-child", and "conditional-blocks" affect the ready work calculation.
// Only blocking types affect the ready work calculation.
func (d DependencyType) AffectsReadyWork() bool {
return d == DepBlocks || d == DepParentChild || d == DepConditionalBlocks
return d == DepBlocks || d == DepParentChild || d == DepConditionalBlocks || d == DepWaitsFor
}
// WaitsForMeta holds metadata for waits-for dependencies (fanout gates).
// Stored as JSON in the Dependency.Metadata field.
type WaitsForMeta struct {
// Gate type: "all-children" (wait for all), "any-children" (wait for first)
Gate string `json:"gate"`
// SpawnerID identifies which step/issue spawns the children to wait for.
// If empty, waits for all direct children of the depends_on_id issue.
SpawnerID string `json:"spawner_id,omitempty"`
}
// WaitsForGate constants
const (
WaitsForAllChildren = "all-children" // Wait for all dynamic children to complete
WaitsForAnyChildren = "any-children" // Proceed when first child completes (future)
)
// FailureCloseKeywords are keywords that indicate an issue was closed due to failure.
// Used by conditional-blocks dependencies to determine if the condition is met.
var FailureCloseKeywords = []string{

View File

@@ -483,6 +483,7 @@ func TestDependencyTypeAffectsReadyWork(t *testing.T) {
{DepBlocks, true},
{DepParentChild, true},
{DepConditionalBlocks, true},
{DepWaitsFor, true},
{DepRelated, false},
{DepDiscoveredFrom, false},
{DepRepliesTo, false},