fix: Make child→parent dep fix opt-in with --fix-child-parent (bd-cuek)
GH#740: bd doctor --fix was auto-removing child→parent dependencies, calling them an 'anti-pattern'. While these often indicate modeling mistakes (deadlock), they may be intentional in some workflows. Changes: - Add --fix-child-parent flag (required to remove child→parent deps) - Remove ChildParentDependencies from default --fix set - Update warning message to reference new flag - Update comments to reflect more nuanced understanding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -48,13 +48,14 @@ type doctorResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
doctorFix bool
|
doctorFix bool
|
||||||
doctorYes bool
|
doctorYes bool
|
||||||
doctorInteractive bool // bd-3xl: per-fix confirmation mode
|
doctorInteractive bool // bd-3xl: per-fix confirmation mode
|
||||||
doctorDryRun bool // bd-a5z: preview fixes without applying
|
doctorDryRun bool // bd-a5z: preview fixes without applying
|
||||||
doctorOutput string // bd-9cc: export diagnostics to file
|
doctorOutput string // bd-9cc: export diagnostics to file
|
||||||
perfMode bool
|
doctorFixChildParent bool // bd-cuek: opt-in fix for child→parent deps
|
||||||
checkHealthMode bool
|
perfMode bool
|
||||||
|
checkHealthMode bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigKeyHintsDoctor is the config key for suppressing doctor hints
|
// ConfigKeyHintsDoctor is the config key for suppressing doctor hints
|
||||||
@@ -104,6 +105,7 @@ Examples:
|
|||||||
bd doctor --fix # Automatically fix issues (with confirmation)
|
bd doctor --fix # Automatically fix issues (with confirmation)
|
||||||
bd doctor --fix --yes # Automatically fix issues (no confirmation)
|
bd doctor --fix --yes # Automatically fix issues (no confirmation)
|
||||||
bd doctor --fix -i # Confirm each fix individually (bd-3xl)
|
bd doctor --fix -i # Confirm each fix individually (bd-3xl)
|
||||||
|
bd doctor --fix --fix-child-parent # Also fix child→parent deps (opt-in)
|
||||||
bd doctor --dry-run # Preview what --fix would do without making changes
|
bd doctor --dry-run # Preview what --fix would do without making changes
|
||||||
bd doctor --perf # Performance diagnostics
|
bd doctor --perf # Performance diagnostics
|
||||||
bd doctor --output diagnostics.json # Export diagnostics to file`,
|
bd doctor --output diagnostics.json # Export diagnostics to file`,
|
||||||
@@ -182,6 +184,7 @@ func init() {
|
|||||||
doctorCmd.Flags().BoolVarP(&doctorYes, "yes", "y", false, "Skip confirmation prompt (for non-interactive use)")
|
doctorCmd.Flags().BoolVarP(&doctorYes, "yes", "y", false, "Skip confirmation prompt (for non-interactive use)")
|
||||||
doctorCmd.Flags().BoolVarP(&doctorInteractive, "interactive", "i", false, "Confirm each fix individually (bd-3xl)")
|
doctorCmd.Flags().BoolVarP(&doctorInteractive, "interactive", "i", false, "Confirm each fix individually (bd-3xl)")
|
||||||
doctorCmd.Flags().BoolVar(&doctorDryRun, "dry-run", false, "Preview fixes without making changes (bd-a5z)")
|
doctorCmd.Flags().BoolVar(&doctorDryRun, "dry-run", false, "Preview fixes without making changes (bd-a5z)")
|
||||||
|
doctorCmd.Flags().BoolVar(&doctorFixChildParent, "fix-child-parent", false, "Remove child→parent dependencies (opt-in, bd-cuek)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// previewFixes shows what would be fixed without applying changes (bd-a5z)
|
// previewFixes shows what would be fixed without applying changes (bd-a5z)
|
||||||
@@ -401,6 +404,11 @@ func applyFixList(path string, fixes []doctorCheck) {
|
|||||||
case "Orphaned Dependencies":
|
case "Orphaned Dependencies":
|
||||||
err = fix.OrphanedDependencies(path)
|
err = fix.OrphanedDependencies(path)
|
||||||
case "Child-Parent Dependencies":
|
case "Child-Parent Dependencies":
|
||||||
|
// bd-cuek: Requires explicit opt-in flag (destructive, may remove intentional deps)
|
||||||
|
if !doctorFixChildParent {
|
||||||
|
fmt.Printf(" ⚠ Child→parent deps require explicit opt-in: bd doctor --fix --fix-child-parent\n")
|
||||||
|
continue
|
||||||
|
}
|
||||||
err = fix.ChildParentDependencies(path)
|
err = fix.ChildParentDependencies(path)
|
||||||
case "Duplicate Issues":
|
case "Duplicate Issues":
|
||||||
// No auto-fix: duplicates require user review
|
// No auto-fix: duplicates require user review
|
||||||
|
|||||||
@@ -162,8 +162,9 @@ func OrphanedDependencies(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChildParentDependencies removes child→parent dependencies (anti-pattern).
|
// ChildParentDependencies removes child→parent blocking dependencies.
|
||||||
// This fixes the deadlock where children depend on their parent epic.
|
// These often indicate a modeling mistake (deadlock: child waits for parent, parent waits for children).
|
||||||
|
// Requires explicit opt-in via --fix-child-parent flag since some workflows may use these intentionally.
|
||||||
func ChildParentDependencies(path string) error {
|
func ChildParentDependencies(path string) error {
|
||||||
if err := validateBeadsWorkspace(path); err != nil {
|
if err := validateBeadsWorkspace(path); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -308,8 +308,9 @@ func CheckTestPollution(path string) DoctorCheck {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckChildParentDependencies detects the child→parent dependency anti-pattern.
|
// CheckChildParentDependencies detects child→parent blocking dependencies.
|
||||||
// This creates a deadlock: child can't start (parent open), parent can't close (children not done).
|
// These often indicate a modeling mistake (deadlock: child waits for parent, parent waits for children).
|
||||||
|
// However, they may be intentional in some workflows, so removal requires explicit opt-in.
|
||||||
func CheckChildParentDependencies(path string) DoctorCheck {
|
func CheckChildParentDependencies(path string) DoctorCheck {
|
||||||
beadsDir := filepath.Join(path, ".beads")
|
beadsDir := filepath.Join(path, ".beads")
|
||||||
dbPath := filepath.Join(beadsDir, beads.CanonicalDatabaseName)
|
dbPath := filepath.Join(beadsDir, beads.CanonicalDatabaseName)
|
||||||
@@ -374,9 +375,9 @@ func CheckChildParentDependencies(path string) DoctorCheck {
|
|||||||
return DoctorCheck{
|
return DoctorCheck{
|
||||||
Name: "Child-Parent Dependencies",
|
Name: "Child-Parent Dependencies",
|
||||||
Status: "warning",
|
Status: "warning",
|
||||||
Message: fmt.Sprintf("%d child→parent dependency anti-pattern(s) detected", len(badDeps)),
|
Message: fmt.Sprintf("%d child→parent dependency detected (may cause deadlock)", len(badDeps)),
|
||||||
Detail: detail,
|
Detail: detail,
|
||||||
Fix: "Run 'bd doctor --fix' to remove child→parent dependencies",
|
Fix: "Run 'bd doctor --fix --fix-child-parent' to remove (if unintentional)",
|
||||||
Category: CategoryMetadata,
|
Category: CategoryMetadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user