Skip to content

feat: add interactive plan editor to move hunks between groups#24

Merged
vitali87 merged 6 commits intomainfrom
feat/interactive-plan-editor
Mar 24, 2026
Merged

feat: add interactive plan editor to move hunks between groups#24
vitali87 merged 6 commits intomainfrom
feat/interactive-plan-editor

Conversation

@vitali87
Copy link
Copy Markdown
Owner

Summary

  • After the plan is displayed, users enter an interactive editor before confirming
  • Commands:
    • move <file>:<hunk_index> <from_group> <to_group> — move a hunk between groups
    • show <group_id> — display group details (files, hunks, deps, stats)
    • plan — redisplay the plan table
    • done — proceed to confirmation
    • abort — cancel
  • Default action is done (just press Enter to skip editing)
  • Validates that source group has the hunk before moving

Test plan

  • All 293 tests pass
  • Manual: run split, use show to inspect groups, move a hunk, plan to verify, done to proceed
  • Manual: press Enter immediately to skip editing (default=done)
  • Manual: type abort to cancel

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience for managing pull request splits by integrating an interactive command-line editor. This new feature empowers users to review and modify the proposed grouping of code changes (hunks) in real-time, providing granular control over the final split plan. It allows for more precise organization of changes, ensuring that each resulting pull request is logically coherent before creation.

Highlights

  • Interactive Editor: Introduced an interactive editor that users enter after the initial plan is displayed, allowing for dynamic adjustments before confirmation.
  • New Commands: Added several commands for the interactive editor: 'move :<hunk_index> <from_group> <to_group>' to reassign hunks, 'show <group_id>' to inspect group details, 'plan' to redisplay the current plan, 'done' to proceed, and 'abort' to cancel.
  • Default Action: The editor defaults to 'done', allowing users to simply press Enter to skip editing and proceed with the current plan.
  • Validation: Implemented validation to ensure that a specified hunk exists in the source group before attempting to move it.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an interactive editor for modifying the split plan, which is a great feature. The implementation is well-structured, but I found a critical issue in the logic for moving hunks between groups. The current implementation doesn't handle all assignment types, which will lead to errors for the user. My review comment provides details on the issue and a path to resolution.

Comment on lines +339 to +378
def _move_assignment(
groups: list[Group], file_path: str, hunk_index: int, from_id: str, to_id: str
) -> bool:
group_map = {g.id: g for g in groups}
src = group_map.get(from_id)
dst = group_map.get(to_id)
if not src or not dst:
console.print(f"[red]Group '{from_id}' or '{to_id}' not found.[/red]")
return False

found = False
for assignment in src.assignments:
if assignment.file_path == file_path and hunk_index in assignment.hunk_indices:
assignment.hunk_indices.remove(hunk_index)
if not assignment.hunk_indices:
src.assignments.remove(assignment)
found = True
break

if not found:
console.print(f"[red]Hunk {file_path}:{hunk_index} not found in {from_id}.[/red]")
return False

dst_assignment = next(
(a for a in dst.assignments if a.file_path == file_path), None
)
if dst_assignment:
dst_assignment.hunk_indices.append(hunk_index)
dst_assignment.hunk_indices.sort()
else:
dst.assignments.append(
GroupAssignment(
file_path=file_path,
assignment_type=AssignmentType.PARTIAL_HUNKS,
hunk_indices=[hunk_index],
)
)

console.print(f"[green]Moved {file_path}:{hunk_index} from {from_id} to {to_id}[/green]")
return True
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The _move_assignment function has a couple of issues that make it fragile and functionally incomplete.

  1. Unsafe list modification: The loop starting at line 350 modifies src.assignments while iterating over it. While the break statement prevents bugs in this specific case, it's a fragile pattern that should be avoided. It's safer to first find the item to modify/remove, and then perform the operation.

  2. WHOLE_FILE assignments not handled: The logic only checks for hunk_index in assignment.hunk_indices. This will fail for assignments of type AssignmentType.WHOLE_FILE, where hunk_indices is not populated. A user trying to move a hunk from a file that was wholly assigned to a group will find that the command fails with a "hunk not found" error. This is a significant functional gap for the interactive editor.

To fix this, _move_assignment needs to be aware of the total number of hunks in a file. This information is in ParsedDiff. This requires a few changes:

  • The split function should pass parsed_diff to _interactive_edit at line 546: groups = _interactive_edit(groups, parsed_diff).
  • The _interactive_edit function signature must be updated to accept parsed_diff and pass it to _move_assignment.
  • This function's (_move_assignment) signature must be updated to accept parsed_diff.

With parsed_diff available, you can refactor this function to correctly handle all cases. A robust implementation would:

  1. Find the source assignment for the given file_path.
  2. Get the patch_file from parsed_diff to know the total_hunks.
  3. Check if the hunk_index is valid and present in the source assignment, considering both WHOLE_FILE and PARTIAL_HUNKS types.
  4. If it's a WHOLE_FILE assignment, convert it to PARTIAL_HUNKS with all hunks except the one being moved.
  5. Remove the hunk from the source assignment's hunk_indices.
  6. If the source assignment becomes empty, remove it from the group.
  7. Add the hunk to the destination group's assignment, creating one if necessary.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 24, 2026

Greptile Summary

This PR introduces an interactive plan editor that lets users inspect and reorganise hunk assignments before committing to a split. After the initial plan is displayed, _interactive_edit opens a REPL-style loop accepting move, show, plan, done, and abort commands; the modified plan is then re-validated (empty-group check + full validate_plan call wrapped in try/except PRSplitError) before execution continues.

Several rounds of fix-up commits have addressed the majority of previously identified issues: the self-move guard, WHOLE_FILEPARTIAL_HUNKS downgrade on both source and destination, mutation-before-validation ordering, duplicate-hunk-index deduplication, negative-index rejection, and per-command argument-count error messages are all handled correctly in the current HEAD.

Remaining concerns (already flagged in earlier review threads, not yet resolved):

  • cmd.strip().split() at line 453 does not handle file paths containing spaces; users with such paths receive a confusing "wrong argument count" error instead of a clear diagnostic. Using shlex.split would fix this.
  • estimated_added, estimated_removed, and estimated_loc fields are never recalculated after moves, so the plan redisplay and show output show stale diff counts. Users may be misled about whether their edited plan stays within the --max-loc limit.

Confidence Score: 4/5

  • Safe to merge; all correctness-critical bugs from earlier review rounds have been addressed, and the two remaining concerns are UX/display issues rather than data-loss or correctness bugs.
  • The fix-up commits systematically resolved every logic error previously flagged (self-move corruption, WHOLE_FILE permanent mutation, destination type pollution, duplicate indices, negative-index path, uncaught validation exception). Post-edit re-validation now catches empty groups and plan-level errors before execution proceeds. The deducted point reflects two known-but-unfixed issues — file paths with spaces silently breaking move, and stale LOC stats in the interactive display — both of which were flagged in prior review threads and have not yet been addressed.
  • No files require special attention; pr_split/cli.py is the only changed file and its correctness-critical paths are sound.

Important Files Changed

Filename Overview
pr_split/cli.py Adds _move_assignment, _show_group_detail, and _interactive_edit functions, plus post-edit re-validation in split. Most previously flagged bugs have been fixed across the fix-up commits (self-move guard, WHOLE_FILE mutation on invalid index, destination WHOLE_FILE downgrade, duplicate-index deduplication, negative-index rejection, post-edit validation wrapped in try/except PRSplitError). Two previously flagged concerns remain unaddressed: file paths containing spaces still break the move command (plain str.split() at line 453), and estimated_added/estimated_removed displayed by plan/show are stale after moves.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[split command] --> B[plan_split → groups]
    B --> C[validate_plan pre-edit]
    C --> D[_present_plan]
    D --> E[_interactive_edit loop]

    E --> F{Read command}
    F -- done / Enter --> G[return groups]
    F -- abort / Ctrl-C --> H[typer.Abort]
    F -- plan --> I[_present_plan groups]
    I --> F
    F -- show group_id --> J[_show_group_detail]
    J --> F
    F -- move file:n from to --> K{Validate args}
    K -- invalid --> L[print usage error]
    L --> F
    K -- valid --> M[_move_assignment]
    M --> N{from_id == to_id?}
    N -- yes --> O[warn, return False]
    O --> F
    N -- no --> P{src/dst groups found?}
    P -- no --> Q[error, return False]
    Q --> F
    P -- yes --> R{src assignment WHOLE_FILE?}
    R -- yes --> S[expand hunk_indices, downgrade to PARTIAL_HUNKS]
    S --> T{hunk in indices?}
    R -- no --> T
    T -- no --> U[Hunk not found error]
    U --> F
    T -- yes --> V[remove hunk from src]
    V --> W{src assignment empty?}
    W -- yes --> X[remove assignment from src]
    W -- no --> Y[dst: find/create assignment]
    X --> Y
    Y --> Z[add hunk_index to dst, sort]
    Z --> AA[print success]
    AA --> F

    G --> AB{empty groups?}
    AB -- yes --> AC[Exit 1]
    AB -- no --> AD[validate_plan post-edit]
    AD --> AE{PRSplitError?}
    AE -- yes --> AF[print error, Exit 1]
    AE -- no --> AG[continue → confirm → create PRs]
Loading

Reviews (6): Last reviewed commit: "fix: reject negative hunk indices, preve..." | Re-trigger Greptile

@vitali87 vitali87 force-pushed the feat/interactive-plan-editor branch from abf9c6f to 256396b Compare March 24, 2026 00:58
@vitali87
Copy link
Copy Markdown
Owner Author

@greptile

@vitali87
Copy link
Copy Markdown
Owner Author

@greptile

@vitali87
Copy link
Copy Markdown
Owner Author

@greptile

except (KeyboardInterrupt, EOFError) as exc:
raise typer.Abort() from exc

parts = cmd.strip().split()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 File paths with spaces silently break the move command

cmd.strip().split() splits on all whitespace without respecting quoting. A file path like src/my components/foo.py would produce 5 tokens instead of 4, triggering the "wrong argument count" error rather than a helpful "file paths with spaces must be quoted" message. Consider using shlex.split to support standard shell quoting conventions:

import shlex
...
        try:
            parts = shlex.split(cmd.strip())
        except ValueError:
            parts = cmd.strip().split()

This allows users to write move "src/my components/foo.py":0 g1 g2 and have it parsed correctly.

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

Comment:
**File paths with spaces silently break the `move` command**

`cmd.strip().split()` splits on all whitespace without respecting quoting. A file path like `src/my components/foo.py` would produce 5 tokens instead of 4, triggering the "wrong argument count" error rather than a helpful "file paths with spaces must be quoted" message. Consider using `shlex.split` to support standard shell quoting conventions:

```python
import shlex
...
        try:
            parts = shlex.split(cmd.strip())
        except ValueError:
            parts = cmd.strip().split()
```

This allows users to write `move "src/my components/foo.py":0 g1 g2` and have it parsed correctly.

How can I resolve this? If you propose a fix, please make it concise.

@vitali87
Copy link
Copy Markdown
Owner Author

@greptile

@vitali87
Copy link
Copy Markdown
Owner Author

@greptile

@vitali87
Copy link
Copy Markdown
Owner Author

@greptileai review

@vitali87 vitali87 merged commit 48f6b6a into main Mar 24, 2026
4 checks passed
@vitali87 vitali87 deleted the feat/interactive-plan-editor branch March 27, 2026 22:48
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