Skip to content

Add recursive close (--recursive/-r) to cascade-close a subtree#133

Merged
jallum merged 2 commits into
mainfrom
beadwork-ep5/recursive-close
May 31, 2026
Merged

Add recursive close (--recursive/-r) to cascade-close a subtree#133
jallum merged 2 commits into
mainfrom
beadwork-ep5/recursive-close

Conversation

@jallum

@jallum jallum commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a --recursive / -r flag to bw close that closes an issue and its entire subtree (all descendants) in a single commit.

  • Leaf-up: descendants close before their parents.
  • Skips already-closed members silently; an already-closed root is tolerated, so a re-run mops up stragglers.
  • Reasons: the root records --reason verbatim; each descendant records closed with parent <root-id>.
  • Unblocks: external issues that become fully unblocked are surfaced and deduped; internal (in-subtree) unblocks are suppressed as noise.
  • Non-recursive path and its CloseResult JSON shape are unchanged (back-compat). Recursive mode emits a new SubtreeCloseResult{Closed, Skipped, Unblocked}.

Implementation

  • internal/issue/subtree_close.go: Store.CloseSubtree(id, reason) + post-order traversal helper. Traversal is built from all issues (including closed) so it passes through closed nodes to reach open descendants.
  • cmd/bw/close.go: --recursive flag, cmdCloseRecursive (single commit via commitWithRetry, one close <id> intent line per closed issue + unblocked lines).
  • cmd/bw/helpers.go: -r--recursive alias.
  • cmd/bw/command.go: flag, description, and example.

Tests

  • Issue-level (subtree_close_test.go): nested grandchildren, leaf-up order, reason propagation, skip-already-closed, already-closed root, external unblock surfaced, internal unblock suppressed.
  • CLI (close_test.go): recursive close, -r alias, skip messaging, JSON shape.
  • Full suite green; smoke-tested in a throwaway repo (single commit verified).

Closes beadwork-ep5.

…work-ep5)

bw close <id> --recursive (-r) closes the issue and every descendant,
leaf-up, in a single commit. Already-closed members are skipped (an
already-closed root is tolerated so a re-run mops up stragglers); the
root keeps its --reason while descendants record 'closed with parent
<root>'. External issues that become unblocked are surfaced and deduped;
internal unblocks are suppressed.

New Store.CloseSubtree returns SubtreeCloseResult{Closed, Skipped,
Unblocked}. The non-recursive path and its CloseResult JSON shape are
unchanged.
@iautom8things iautom8things self-requested a review May 30, 2026 23:08

@iautom8things iautom8things left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 🚀 🚀

@iautom8things

Copy link
Copy Markdown
Collaborator

So something I just thought of, after trying this out: We should add this to delete as well.

@jallum

jallum commented May 31, 2026

Copy link
Copy Markdown
Owner Author

So something I just thought of, after trying this out: We should add this to delete as well.

yes, indeed we should. that's a good call.

Mirrors the recursive close in this PR for delete, per review feedback:
delete an issue and its entire subtree (all descendants) leaf-up in a
single commit (one "delete <id>" intent line per removed issue, reusing
the existing delete verb for deterministic replay).

- Store: DeleteSubtree + DeleteSubtreePreview + SubtreeDeleteResult in
  internal/issue/subtree_delete.go, reusing buildSubtreeSet/subtreePostOrder.
  External dependents that become fully unblocked are surfaced and deduped;
  internal (in-subtree) unblocks are suppressed as noise.
- CLI: delete grows --recursive/-r. Like the non-recursive path it previews
  by default; --force commits.
- Tests: store-level (nested leaf-up order, deletes open+closed members,
  pass-through closed node, external unblock surfaced, internal unblock
  suppressed, still-blocked dependent not surfaced, preview mutates nothing)
  and CLI (preview->force, -r alias, --json shape).

Follow-up to beadwork-ep5.
@jallum

jallum commented May 31, 2026

Copy link
Copy Markdown
Owner Author

Good call — added --recursive/-r to delete as well in 5e2320c, mirroring the recursive close in this PR.

  • Store: DeleteSubtree + DeleteSubtreePreview (internal/issue/subtree_delete.go), reusing the buildSubtreeSet/subtreePostOrder helpers. Leaf-up removal; external dependents that become fully unblocked are surfaced + deduped, internal unblocks suppressed.
  • CLI: like the non-recursive path, it previews by default and --force commits — one delete <id> intent line per removed issue (reuses the existing delete verb, so replay/sync stays deterministic).
  • Tests: store-level (leaf-up order, deletes open+closed members, passes through a closed node to reach open descendants, external-unblock surfaced, internal-unblock suppressed, still-blocked dependent not surfaced, preview mutates nothing) + CLI (preview→force, -r alias, JSON shape).

Smoke-tested end-to-end: preview mutates nothing, -r --force removes the whole subtree in a single commit. Full suite + go vet green.

@iautom8things iautom8things self-requested a review May 31, 2026 12:10
@jallum jallum merged commit ad6b252 into main May 31, 2026
1 check passed
@jallum jallum deleted the beadwork-ep5/recursive-close branch May 31, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants