feat: implement conditional bond type for mol bond (bd-kzda)

Conditional bonds now work as documented: "B runs only if A fails".

Implementation:
- Add DepConditionalBlocks dependency type to types.go
- Add IsFailureClose() helper to detect failure keywords in close_reason
- Update blocked cache to handle conditional-blocks:
  - B is blocked while A is open
  - B stays blocked if A closes with success
  - B becomes unblocked if A closes with failure

Failure keywords: failed, rejected, wontfix, cancelled, abandoned,
blocked, error, timeout, aborted (case-insensitive)

Updated bondProtoProto, bondProtoMol, bondMolMol to use
DepConditionalBlocks for conditional bond type.

🤖 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 00:26:29 -08:00
parent aa1ce63156
commit 7fb92ff78c
9 changed files with 261 additions and 30 deletions

View File

@@ -301,12 +301,18 @@ func bondProtoProto(ctx context.Context, s storage.Storage, protoA, protoB *type
return fmt.Errorf("linking proto B: %w", err)
}
// For sequential bonding, add blocking dependency: B blocks on A
if bondType == types.BondTypeSequential {
// For sequential/conditional bonding, add blocking dependency: B blocks on A
// Sequential: B runs after A completes (any outcome)
// Conditional: B runs only if A fails (bd-kzda)
if bondType == types.BondTypeSequential || bondType == types.BondTypeConditional {
depType := types.DepBlocks
if bondType == types.BondTypeConditional {
depType = types.DepConditionalBlocks
}
seqDep := &types.Dependency{
IssueID: protoB.ID,
DependsOnID: protoA.ID,
Type: types.DepBlocks,
Type: depType,
}
if err := tx.AddDependency(ctx, seqDep, actorName); err != nil {
return fmt.Errorf("adding sequence dep: %w", err)
@@ -357,12 +363,18 @@ func bondProtoMol(ctx context.Context, s storage.Storage, proto, mol *types.Issu
// Attach spawned molecule to existing molecule
err = s.RunInTransaction(ctx, func(tx storage.Transaction) error {
// Add dependency from spawned root to molecule
// For sequential: use blocks (captures workflow semantics)
// For parallel/conditional: use parent-child (organizational)
// Sequential: use blocks (B runs after A completes)
// Conditional: use conditional-blocks (B runs only if A fails) (bd-kzda)
// Parallel: use parent-child (organizational, no blocking)
// Note: Schema only allows one dependency per (issue_id, depends_on_id) pair
depType := types.DepParentChild
if bondType == types.BondTypeSequential {
var depType types.DependencyType
switch bondType {
case types.BondTypeSequential:
depType = types.DepBlocks
case types.BondTypeConditional:
depType = types.DepConditionalBlocks
default:
depType = types.DepParentChild
}
dep := &types.Dependency{
IssueID: spawnResult.NewEpicID,
@@ -397,12 +409,18 @@ func bondMolProto(ctx context.Context, s storage.Storage, mol, proto *types.Issu
func bondMolMol(ctx context.Context, s storage.Storage, molA, molB *types.Issue, bondType, actorName string) (*BondResult, error) {
err := s.RunInTransaction(ctx, func(tx storage.Transaction) error {
// Add dependency: B links to A
// For sequential: use blocks (captures workflow semantics)
// For parallel/conditional: use parent-child (organizational)
// Sequential: use blocks (B runs after A completes)
// Conditional: use conditional-blocks (B runs only if A fails) (bd-kzda)
// Parallel: use parent-child (organizational, no blocking)
// Note: Schema only allows one dependency per (issue_id, depends_on_id) pair
depType := types.DepParentChild
if bondType == types.BondTypeSequential {
var depType types.DependencyType
switch bondType {
case types.BondTypeSequential:
depType = types.DepBlocks
case types.BondTypeConditional:
depType = types.DepConditionalBlocks
default:
depType = types.DepParentChild
}
dep := &types.Dependency{
IssueID: molB.ID,

View File

@@ -1041,6 +1041,7 @@ func TestSpawnAttachTypes(t *testing.T) {
}{
{"sequential uses blocks", types.BondTypeSequential, types.DepBlocks},
{"parallel uses parent-child", types.BondTypeParallel, types.DepParentChild},
{"conditional uses conditional-blocks", types.BondTypeConditional, types.DepConditionalBlocks},
}
for _, tt := range tests {