Refactor burn/squash cleanup to recursive helper (gt-psj76.1)

Extract closeDescendants() helper that:
- Recursively closes all descendants (not just direct children)
- Logs errors instead of silent failure
- Uses distinct variable names (no shadowing)
- Is reused by both burn and squash

This handles nested molecules from the 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-25 21:30:38 -08:00
parent a293b0f152
commit 7d4a5de697

View File

@@ -337,30 +337,9 @@ func runMoleculeBurn(cmd *cobra.Command, args []string) error {
moleculeID := attachment.AttachedMolecule
// Close all child step issues before detaching
// Recursively close all descendant step issues before detaching
// This prevents orphaned step issues from accumulating (gt-psj76.1)
childrenClosed := 0
children, err := b.List(beads.ListOptions{
Parent: moleculeID,
Status: "all", // Include both open and in_progress
})
if err == nil && len(children) > 0 {
var idsToClose []string
for _, child := range children {
if child.Status != "closed" {
idsToClose = append(idsToClose, child.ID)
}
}
if len(idsToClose) > 0 {
if err := b.Close(idsToClose...); err != nil {
// Log but don't fail - best effort cleanup
fmt.Printf("%s Warning: could not close all step issues: %v\n",
style.Dim.Render("⚠"), err)
} else {
childrenClosed = len(idsToClose)
}
}
}
childrenClosed := closeDescendants(b, moleculeID)
// Detach the molecule with audit logging (this "burns" it by removing the attachment)
_, err = b.DetachMoleculeWithAudit(handoff.ID, beads.DetachOptions{
@@ -462,30 +441,9 @@ func runMoleculeSquash(cmd *cobra.Command, args []string) error {
moleculeID := attachment.AttachedMolecule
// Close all child step issues before squashing
// Recursively close all descendant step issues before squashing
// This prevents orphaned step issues from accumulating (gt-psj76.1)
childrenClosed := 0
children, err := b.List(beads.ListOptions{
Parent: moleculeID,
Status: "all", // Include both open and in_progress
})
if err == nil && len(children) > 0 {
var idsToClose []string
for _, child := range children {
if child.Status != "closed" {
idsToClose = append(idsToClose, child.ID)
}
}
if len(idsToClose) > 0 {
if err := b.Close(idsToClose...); err != nil {
// Log but don't fail - best effort cleanup
fmt.Printf("%s Warning: could not close all step issues: %v\n",
style.Dim.Render("⚠"), err)
} else {
childrenClosed = len(idsToClose)
}
}
}
childrenClosed := closeDescendants(b, moleculeID)
// Get progress info for the digest
progress, _ := getMoleculeProgressInfo(b, moleculeID)
@@ -569,3 +527,46 @@ squashed_at: %s
return nil
}
// closeDescendants recursively closes all descendant issues of a parent.
// Returns the count of issues closed. Logs warnings on errors but doesn't fail.
func closeDescendants(b *beads.Beads, parentID string) int {
children, err := b.List(beads.ListOptions{
Parent: parentID,
Status: "all",
})
if err != nil {
fmt.Printf("%s Warning: could not list children of %s: %v\n",
style.Dim.Render("⚠"), parentID, err)
return 0
}
if len(children) == 0 {
return 0
}
// First, recursively close grandchildren
totalClosed := 0
for _, child := range children {
totalClosed += closeDescendants(b, child.ID)
}
// Then close direct children
var idsToClose []string
for _, child := range children {
if child.Status != "closed" {
idsToClose = append(idsToClose, child.ID)
}
}
if len(idsToClose) > 0 {
if closeErr := b.Close(idsToClose...); closeErr != nil {
fmt.Printf("%s Warning: could not close children of %s: %v\n",
style.Dim.Render("⚠"), parentID, closeErr)
} else {
totalClosed += len(idsToClose)
}
}
return totalClosed
}