Skip to content

feat: stacked PR mode with native GitHub stack linking and draft PRs - #42

Merged
vitali87 merged 16 commits into
mainfrom
feat/stacked-pr-mode
Jul 30, 2026
Merged

feat: stacked PR mode with native GitHub stack linking and draft PRs#42
vitali87 merged 16 commits into
mainfrom
feat/stacked-pr-mode

Conversation

@vitali87

@vitali87 vitali87 commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Adds --stack so dependent sub-PRs branch from and target their parent's branch, with linear chains registered as native GitHub stacks via gh-stack, plus --draft for draft sub-PRs. Also fixes added-file materialisation double-spacing every new file.

Summary by CodeRabbit

  • New Features
    • Added stacked pull request creation for linear dependency chains and improved stack linking in GitHub for chained PRs.
    • Added --stack and --draft CLI options, including when executing saved plans, plus matching configuration via environment variables.
  • Bug Fixes
    • Improved diff reconstruction for ancestor/partial hunks and corrected newline handling when materializing new files.
    • PR creation is now properly gated on pushed base branches, and draft/stack behavior is consistently applied across the created PRs.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@vitali87, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ba47f3b-9b0c-42b1-b066-442c653e66bd

📥 Commits

Reviewing files that changed from the base of the PR and between 65125b0 and 1984630.

📒 Files selected for processing (5)
  • pr_split/cli.py
  • pr_split/planner/__init__.py
  • pr_split/planner/validator.py
  • tests/test_cli_helpers.py
  • tests/test_validator.py
📝 Walkthrough

Walkthrough

The CLI adds stacked dependency branches, draft PR creation, native stack linking, and saved-plan overrides. DAG and diff reconstruction support linear chains and ancestor hunks, while schemas, GitHub operations, tests, and documentation cover the new behavior.

Changes

Stacked PR workflow

Layer / File(s) Summary
Chain and plan foundations
pr_split/graph.py, pr_split/diff_ops/..., pr_split/schemas.py, tests/test_graph.py, tests/test_reconstructor.py, tests/test_schemas.py
DAGs calculate linear chains, ancestor partial hunks are merged, added-file newlines are preserved, and plans store stacked and draft values.
Stacked branch and commit creation
pr_split/cli.py, tests/test_cli_helpers.py, pr_split/logs.py
Stacked mode creates dependency-ordered branches from parent refs while retaining flat-mode behavior and handling merge nodes against the base branch.
Draft PR publication and stack linking
pr_split/cli.py, pr_split/git_ops/prs.py, pr_split/exceptions.py, pr_split/logs.py, tests/test_cli_helpers.py, tests/test_git_prs.py
Branches are pushed before PR creation, draft flags reach gh pr create, partial PR records are preserved on failure, and linear PR chains are linked through gh stack link.
CLI options and documentation
pr_split/cli.py, README.md
split and execute accept stacked and draft options, saved plans persist or override them, and CLI and environment configuration documentation is updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant CLI
  participant PlanDAG
  participant Git
  participant GitHub
  Developer->>CLI: run split or execute with --stack/--draft
  CLI->>PlanDAG: resolve dependency-ready groups and chains
  PlanDAG->>Git: create branches from parent refs
  Git-->>CLI: return branch and commit results
  CLI->>Git: push successful branches
  CLI->>GitHub: create draft or regular PRs
  CLI->>GitHub: link linear PR stacks
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: stacked PR mode with GitHub stack linking and draft PR support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/stacked-pr-mode

Comment @coderabbitai help to get the list of available commands.

Comment thread pr_split/diff_ops/reconstructor.py Outdated
Comment thread pr_split/cli.py
Comment thread pr_split/cli.py Outdated
@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown

Greptile Summary

Adds stacked and draft sub-PR creation, composes ancestor changes for stacked branches, persists successfully created PR records after partial failures, and improves whole-file coverage handling.

Saved-plan execution was exercised with corrupted persisted plans. Overlapping assignments are rejected, but a plan with complete coverage and cyclic dependencies still reaches branch creation rather than being rejected before repository mutation. This should be corrected before merge.

Confidence Score: 3/5

Not safe to merge until T-Rex findings are addressed.

The saved-plan execution flow accepts a cyclic dependency graph and reaches the branch-creation boundary. This was reproduced using the real persisted plan format and execute command with external side effects isolated.

T-Rex reproduced 2 failing behaviors at runtime in pr_split/cli.py; the change needs fixes before it is safe to merge.

Files Needing Attention: pr_split/cli.py needs complete plan validation in execute before presenting the plan, requesting confirmation, or creating branches.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced a finding-comment-proof for the posted P1 finding and attached the focused saved-plan execute validation regression harness artifact along with the relevant corruption logs for review.
  • T-Rex produced a second finding-comment-proof for the same P1 finding to reinforce the validation with additional corroboration.
  • T-Rex produced a general-contract-validation-proof detailing the scope of contract validation, noting that the code path validates only coverage, that cycles are not fully guarded and that the parent revision allows corrupted plans while the PR fixed only overlaps.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Execute permits cyclic saved plans to reach branch mutation

    • Bug
      • A manually corrupted saved dry-run plan with groups A → B and B → A has complete, non-overlapping hunk coverage. pr-split execute accepts it and invokes the branch-creation boundary instead of rejecting it as an invalid DAG.
    • Cause
      • execute invokes validate_coverage only at pr_split/cli.py:1045-1049; complete saved-plan validation at pr_split/planner/validator.py:133-144 also requires DAG acyclicity and conflict validation, but is not called.
    • Fix
      • Before presenting the plan or confirming execution, construct PlanDAG(plan.groups) and invoke the complete validate_plan(plan.groups, parsed_diff, dag, plan.max_loc, plan.min_loc) validation path, handling its loc-bound warnings consistently with plan creation.

    T-Rex Ran code and verified through T-Rex

Prompt To Fix All With AI
### Issue 1
pr_split/cli.py:1045-1049
**Saved-plan cycle validation is skipped**

`execute` only calls `validate_coverage`, so a saved dry-run plan with complete, non-overlapping coverage but a dependency cycle is accepted and reaches branch creation. A manually edited or otherwise corrupted `.pr-split/plan.json` can therefore begin mutating the repository instead of failing as an invalid plan. Construct `PlanDAG(plan.groups)` and run the complete `validate_plan` path before presenting the plan or requesting confirmation.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (6): Last reviewed commit: "fix: treat whole-file assignments as ful..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/test_schemas.py (1)

55-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add equivalent draft persistence coverage.

draft is persisted alongside stacked, but only stacked gets default and round-trip assertions. Add draft=False and draft=True cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_schemas.py` around lines 55 - 70, Add equivalent draft persistence
coverage in TestSplitPlanStacked: extend the default case to assert draft
defaults to False and the plan-file round-trip case to construct with draft=True
and assert it remains True after model validation.
tests/test_cli_helpers.py (1)

245-360: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a 3-level chain test for transitive ancestor-hunk merging.

Current stacked tests only cover depth-2 (single parent → child) merging. The trickiest part of _stacked_batch_args is that effective[parent] already carries hunks merged from its ancestors, so a grandchild's merge should pick up hunks from both its parent and grandparent transitively. A 3-level chain test (grandparent → parent → child, each touching the same file) would directly validate this composition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_cli_helpers.py` around lines 245 - 360, Extend
TestCreateBranchesAndCommitsStacked with a three-level stacked chain where
grandparent, parent, and child each assign distinct partial hunks to the same
file. Invoke _create_branches_and_commits in stacked mode, inspect
materialize_group_files calls for the child, and assert its effective assignment
includes all three hunk indices, validating transitive ancestor-hunk merging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pr_split/cli.py`:
- Around line 412-448: Update _push_and_create_prs so PR creation only includes
a group when its own branch and every ancestor/base branch required by the stack
were pushed successfully. Propagate push failure through the parent relationship
before submitting _create_single_pr, while preserving the existing error
collection and successful independent branches.

---

Nitpick comments:
In `@tests/test_cli_helpers.py`:
- Around line 245-360: Extend TestCreateBranchesAndCommitsStacked with a
three-level stacked chain where grandparent, parent, and child each assign
distinct partial hunks to the same file. Invoke _create_branches_and_commits in
stacked mode, inspect materialize_group_files calls for the child, and assert
its effective assignment includes all three hunk indices, validating transitive
ancestor-hunk merging.

In `@tests/test_schemas.py`:
- Around line 55-70: Add equivalent draft persistence coverage in
TestSplitPlanStacked: extend the default case to assert draft defaults to False
and the plan-file round-trip case to construct with draft=True and assert it
remains True after model validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a0f9615f-c60b-4744-876d-f6a0e19fd6c2

📥 Commits

Reviewing files that changed from the base of the PR and between 2686464 and 0485167.

📒 Files selected for processing (13)
  • README.md
  • pr_split/cli.py
  • pr_split/diff_ops/__init__.py
  • pr_split/diff_ops/reconstructor.py
  • pr_split/git_ops/prs.py
  • pr_split/graph.py
  • pr_split/logs.py
  • pr_split/schemas.py
  • tests/test_cli_helpers.py
  • tests/test_git_prs.py
  • tests/test_graph.py
  • tests/test_reconstructor.py
  • tests/test_schemas.py

Comment thread pr_split/cli.py
@vitali87

Copy link
Copy Markdown
Owner Author

@greptile review

Comment thread pr_split/cli.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pr_split/cli.py (1)

423-485: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

PR-creation failures aren't propagated to dependent children (distinct from the already-fixed push-failure gap).

_push_and_create_prs now correctly gates PR creation on push success via _base_pushed (past issue fixed). However, the second phase still submits _create_single_pr for every group whose base was pushed inside a single flat ThreadPoolExecutor batch, with no ordering by dependency. If a parent's own _create_single_pr call fails (e.g. a transient gh pr create error) after its branch was successfully pushed, its child is still submitted concurrently and can succeed — producing a child PR whose base branch has no corresponding PR. PRCreationError then aborts before _link_stacks runs, so this broken chain persists on GitHub with no automatic remediation. None of the added tests exercise a dependent (parent/child) PR-creation failure — test_partial_pr_records_ride_on_the_error and test_child_pr_skipped_when_parent_push_fails both use independent groups.

Consider processing PR creation in dependency-ordered batches (similar to _create_branches_and_commits's use of dag.iter_ready()), only submitting a group once its base's PR (if part of the stack) has been created successfully.

Do you want me to draft the batched implementation and a regression test for this scenario?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pr_split/cli.py` around lines 423 - 485, The PR creation phase must propagate
parent PR failures to dependent children instead of submitting all eligible
groups concurrently. Update _push_and_create_prs to process groups in
dependency-ordered batches, using the stack DAG or equivalent readiness
tracking, and submit a group only after its base branch’s owning group has
created a PR successfully; retain independent-group concurrency and existing
error aggregation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@pr_split/cli.py`:
- Around line 423-485: The PR creation phase must propagate parent PR failures
to dependent children instead of submitting all eligible groups concurrently.
Update _push_and_create_prs to process groups in dependency-ordered batches,
using the stack DAG or equivalent readiness tracking, and submit a group only
after its base branch’s owning group has created a PR successfully; retain
independent-group concurrency and existing error aggregation behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 82fef5a8-819b-45bd-bc99-28cbee085267

📥 Commits

Reviewing files that changed from the base of the PR and between 0485167 and 65125b0.

📒 Files selected for processing (8)
  • README.md
  • pr_split/cli.py
  • pr_split/diff_ops/reconstructor.py
  • pr_split/exceptions.py
  • pr_split/logs.py
  • tests/test_cli_helpers.py
  • tests/test_reconstructor.py
  • tests/test_schemas.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • README.md
  • pr_split/logs.py

Comment thread pr_split/diff_ops/reconstructor.py
@vitali87
vitali87 merged commit df32dce into main Jul 30, 2026
6 checks passed
@vitali87

Copy link
Copy Markdown
Owner Author

@greptile review

Comment thread pr_split/cli.py
Comment on lines +1045 to +1049
try:
validate_coverage(plan.groups, parsed_diff)
except PlanValidationError as exc:
console.print(f"[red]{exc}[/red]")
raise typer.Exit(1) from exc

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Saved-plan cycle validation is skipped

execute only calls validate_coverage, so a saved dry-run plan with complete, non-overlapping coverage but a dependency cycle is accepted and reaches branch creation. A manually edited or otherwise corrupted .pr-split/plan.json can therefore begin mutating the repository instead of failing as an invalid plan. Construct PlanDAG(plan.groups) and run the complete validate_plan path before presenting the plan or requesting confirmation.

Artifacts

Focused saved-plan execute validation regression harness

  • Authored harness writes corrupted saved plans and invokes the real execute command while isolating external preconditions and the mutation boundary, proving the validation scope.

Parent revision corrupted saved-plan execution log

  • Executed the harness against the parent revision; both cyclic and overlapping plans reached the mocked branch-creation boundary, showing the pre-PR behavior.

PR head corrupted saved-plan execution log

  • Executed the harness against the PR head; overlapping assignments are rejected but a cyclic plan still reaches the mocked branch-creation boundary, proving the remaining defect.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: pr_split/cli.py
Line: 1045-1049

Comment:
**Saved-plan cycle validation is skipped**

`execute` only calls `validate_coverage`, so a saved dry-run plan with complete, non-overlapping coverage but a dependency cycle is accepted and reaches branch creation. A manually edited or otherwise corrupted `.pr-split/plan.json` can therefore begin mutating the repository instead of failing as an invalid plan. Construct `PlanDAG(plan.groups)` and run the complete `validate_plan` path before presenting the plan or requesting confirmation.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

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.

1 participant