feat(deps): detect/prevent child→parent dependency anti-pattern (bd-nim5)
This prevents a common mistake where users add dependencies from child issues to their parent epics. This creates a deadlock: - Child can't start (blocked by open parent) - Parent can't close (children not done) Changes: - dep.go: Reject child→parent deps at creation time with clear error - server_labels_deps_comments.go: Same check for daemon RPC - doctor/validation.go: New check detects existing bad deps - doctor/fix/validation.go: Auto-fix removes bad deps - doctor.go: Wire up check and fix handler 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -956,3 +956,86 @@ func TestMergeBidirectionalTrees_PreservesDepth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for child→parent dependency detection (bd-nim5)
|
||||
func TestIsChildOf(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
childID string
|
||||
parentID string
|
||||
want bool
|
||||
}{
|
||||
// Positive cases: should be detected as child
|
||||
{
|
||||
name: "direct child",
|
||||
childID: "bd-abc.1",
|
||||
parentID: "bd-abc",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "grandchild",
|
||||
childID: "bd-abc.1.2",
|
||||
parentID: "bd-abc",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "nested grandchild direct parent",
|
||||
childID: "bd-abc.1.2",
|
||||
parentID: "bd-abc.1",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "deeply nested child",
|
||||
childID: "bd-abc.1.2.3",
|
||||
parentID: "bd-abc",
|
||||
want: true,
|
||||
},
|
||||
|
||||
// Negative cases: should NOT be detected as child
|
||||
{
|
||||
name: "same ID",
|
||||
childID: "bd-abc",
|
||||
parentID: "bd-abc",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "not a child - unrelated IDs",
|
||||
childID: "bd-xyz",
|
||||
parentID: "bd-abc",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "not a child - sibling",
|
||||
childID: "bd-abc.2",
|
||||
parentID: "bd-abc.1",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "reversed - parent is not child of child",
|
||||
childID: "bd-abc",
|
||||
parentID: "bd-abc.1",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "prefix but not hierarchical",
|
||||
childID: "bd-abcd",
|
||||
parentID: "bd-abc",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "not hierarchical ID",
|
||||
childID: "bd-abc",
|
||||
parentID: "bd-xyz",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := isChildOf(tt.childID, tt.parentID)
|
||||
if got != tt.want {
|
||||
t.Errorf("isChildOf(%q, %q) = %v, want %v", tt.childID, tt.parentID, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user