Skip to content

Merge train: land all 44 open PRs - #243

Merged
vitali87 merged 143 commits into
mainfrom
claude/nice-gauss-whhkxl
Sep 27, 2026
Merged

vitali87 merged 143 commits into
mainfrom
claude/nice-gauss-whhkxl

Conversation

@vitali87

Copy link
Copy Markdown
Owner

Lands every open PR in one merge, so CI and the branch-up-to-date rule only have to pass once instead of 44 times in a row. Each PR branch was first updated with current main (merge commits, no force-push), then all were merged here. Every PR head is an ancestor of this branch, so GitHub marks each PR merged once this lands.

Included

Changes made while merging

Checks

ruff check, ruff format --check, ty check pr_split (20 diagnostics, same as main) and pytest (1015 passed) all pass locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w


Generated by Claude Code

_render_dag and _render_dag_markdown walked the DAG as a tree, so a
group with several parents (and its whole subtree, and its '<-- this
PR' marker) appeared once per parent. The merge node is now drawn in
full under its last-listed parent with '(also depends on: ...)' and
referenced as '(see below)' under the others.
A chmod-only change produces a diff entry with no hunks. Like binary
files, nothing in the plan can claim it, coverage validation passes,
and the mode change is missing from every sub-PR. Reject it up front
with the same kind of message as binary files.
The '(see below)' stub was placed under every parent except depends_on[-1],
but the DFS visits parents in group order, so the stub could appear after
the full node. Render in full under the last parent actually visited and
drop the unreachable 'rendered' guard.
fetch_fork_pr and fetch_fork_branch fetched into refs/pr-split/* without
a leading '+', so once the ref existed from a previous run any rebased or
force-pushed fork head was rejected as non-fast-forward and the split
failed with PR_FETCH_FAILED. Use a forced refspec.
A parent that is never visited (it depends on an unknown group) left its
child stubbed forever with no full rendering. Compute the pending set
from a root-first reachability walk.
The chunk file summary said 'indices 0..N-1' where N was the number of
that file's hunks in the chunk, while the diff labels (and the system
prompt's instructions) use per-file global indices. For a file split
across chunks the summary told the model to use indices already
assigned in an earlier chunk. FileSummary now carries the actual
indices and the summary lists them when they are not 0..N-1.
derive_split_namespace could return an empty string (branch
'pr-split//pr-1', rejected by git), emit '..' or edge dots, and mapped
user:feat/x and a local feat-x to the same namespace so one split's
add_worktree force-deleted the other's branches. Keep the fork user in
the namespace, strip ref-invalid sequences, and fall back to a short
hash when nothing survives.
Setting any permission in a workflow resets the others to none, so the
example's pull-requests: write alone leaves actions/checkout without
read access on private repos. The repo's own split-score.yml already
includes contents: read.
Tracking 'done' parents was wrong in both directions (a mid-walk
ancestor is neither done nor pending). Count visits instead: every
reachable parent is drawn exactly once, so a child is stubbed until its
final visit and drawn in full there.
score_pr.py materialised the PR's base and head as local branches named
after them. A fork PR opened from the fork's own 'main' has
HEAD_BRANCH == BASE_BRANCH, so the second 'git branch -f' pointed the
base at the PR head, the diff came out empty, and the PR was reported as
within limits. Pass origin/<base> and the fetched head ref straight to
pr-split instead; rev-parse --verify accepts any ref.
…shold

The comment step only ran when should_split was true, so once a PR was
pushed back under the limit its old '<!-- pr-split-score -->' comment
with the split plan stayed. The script now writes a 'within limits'
body on every skip, and the action updates an existing marker comment
regardless of should_split while still only creating one when a split
is suggested. Non-PR events are skipped in the step itself.
scripts/score_pr.py computes overflow*1000 + file_scatter*50 + groups,
not the full score_plan objective (underflow, edges, tiny groups,
depth/width) that README and METHODOLOGY describe; say so.
… path

Failure-path skips (pr-split error, missing plan) must not produce a
body, or a transient failure would overwrite a valid split-plan comment
with a false 'within limits' message.
unidiff's is_removed_file is true for any single hunk whose target
length is 0, which also matches a file emptied on the dev branch. The
sub-PR then deleted the file, its tree differed from dev, and merging
it conflicted (modify/delete). Treat only a /dev/null target as a
deletion.
A chmod that came with a content change passed validation but the
worker only wrote file content, so the executable bit never reached
the sub-PR. Read new mode / new file mode from the diff header and
chmod the materialized file.
A symlink in the diff (new file mode 120000, or index ... 120000 for a
retargeted link) was written as a regular file containing the target
path plus a newline, so the sub-PR changed the entry type. Read the
mode from the index line too, create symlinks with symlink_to, never
write through an existing link, and unlink dangling links on removal.
A get_pr_state failure was recorded as a skip and the run still ended
with exit 0 and 'Merge complete', so a broken gh session or transient
API error looked like success to scripts and webhooks. Report the
affected groups, exit 1, and send exit_reason=fetch_error.
A response with status=failed carries no function_call, so it was
reported as 'no function_call in response output' and the real error
(rate limit, server error) was lost.
The example used @main while the repo's own split-score.yml pins
@v1.0.0; a floating ref means downstream repos pick up unreleased
action changes. Match the shipped workflow.
A PR closed while --auto polled was discarded silently: the batch was
reported as incomplete with an empty skipped list. Record it as
'(CLOSED)' like a closed PR found before merging.
Every non-tool_use stop reason was logged as 'LLM output truncated',
mislabelling end_turn/stop_sequence responses that carried a complete
tool block.
…to claude/nice-gauss-whhkxl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
… claude/nice-gauss-whhkxl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
…aude/nice-gauss-whhkxl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
…o claude/nice-gauss-whhkxl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
…aude/nice-gauss-whhkxl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
…to claude/nice-gauss-whhkxl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
…aude/nice-gauss-whhkxl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
…' into claude/nice-gauss-whhkxl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
… into claude/nice-gauss-whhkxl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
pr-split now saves plans under .pr-split/plans/<branch>.json, so the score
action looked for .pr-split/plan.json, never found it, and reported every
oversized PR as "pr-split wrote no plan file". Read the per-branch file,
falling back to the legacy path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
@coderabbitai

coderabbitai Bot commented Sep 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 36 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 3a4d3733-26d2-4d4f-bb82-8f9843e9eee1

📥 Commits

Reviewing files that changed from the base of the PR and between 671a081 and 0347578.

📒 Files selected for processing (42)
  • README.md
  • action.yml
  • pr_split/cli.py
  • pr_split/config.py
  • pr_split/diff_ops/__init__.py
  • pr_split/diff_ops/parser.py
  • pr_split/diff_ops/reconstructor.py
  • pr_split/exceptions.py
  • pr_split/git_ops/__init__.py
  • pr_split/git_ops/branches.py
  • pr_split/git_ops/prs.py
  • pr_split/logs.py
  • pr_split/planner/chunker.py
  • pr_split/planner/client.py
  • pr_split/planner/partitioning.py
  • pr_split/planner/prompts.py
  • pr_split/planner/validator.py
  • pr_split/types_defs.py
  • scripts/score_pr.py
  • tests/conftest.py
  • tests/network_guards.py
  • tests/test_chunker.py
  • tests/test_chunker_exhaustive.py
  • tests/test_cli_coverage.py
  • tests/test_cli_helpers.py
  • tests/test_cli_new_features.py
  • tests/test_client.py
  • tests/test_config.py
  • tests/test_diff_parser_extended.py
  • tests/test_exceptions.py
  • tests/test_git_branches.py
  • tests/test_git_prs.py
  • tests/test_git_prs_coverage.py
  • tests/test_partitioning.py
  • tests/test_prompts.py
  • tests/test_reconstructor.py
  • tests/test_score_pr_comment.py
  • tests/test_score_pr_events.py
  • tests/test_score_pr_refs.py
  • tests/test_score_pr_table.py
  • tests/test_types_defs.py
  • tests/test_validator.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

Copy link
Copy Markdown

pr-split analysis

Metric Value
Total LOC 4,187
Files changed 42
Suggested groups 22
Largest group 400 LOC
LOC overflow 0
File scatter 4

This PR has 4,187 LOC and could be split into 22 smaller PRs:

Group Title Diff Depends On Files
pr-1 chore(split): review cli-1 +333/-59 — pr_split/cli.py
pr-2 chore(split): review test-git-branches-2 +352/-17 — tests/test_git_branches.py
pr-3 chore(split): review test-cli-helpers-3 +359/-0 — tests/test_cli_helpers.py
pr-4 chore(split): review test-cli-coverage-4 +346/-3 — tests/test_cli_coverage.py
pr-5 chore(split): review test-cli-new-features-5 +313/-0 — tests/test_cli_new_features.py
pr-6 chore(split): review test-cli-coverage-6 +299/-0 pr-4 tests/test_cli_coverage.py
pr-7 chore(split): review-slice-7 +355/-45 — pr_split/diff_ops/parser.py, pr_split/diff_ops/reconstructor.py, pr_split/git_ops/__init__.py, pr_split/git_ops/branches.py, pr_split/git_ops/prs.py, pr_split/types_defs.py
pr-8 chore(split): review test-cli-coverage-8 +166/-0 pr-6 tests/test_cli_coverage.py
pr-9 chore(split): review cli-9 +132/-32 pr-1 pr_split/cli.py
pr-10 chore(split): review test-git-prs-10 +134/-0 — tests/test_git_prs.py
pr-11 chore(split): review test-diff-parser-extended-11 +124/-0 — tests/test_diff_parser_extended.py
pr-12 chore(split): review test-reconstructor-12 +123/-0 — tests/test_reconstructor.py
pr-13 chore(split): review test-score-pr-table-13 +113/-0 — tests/test_score_pr_table.py
pr-14 chore(split): review test-git-branches-14 +105/-0 pr-2 tests/test_git_branches.py
pr-15 chore(split): review test-git-prs-coverage-15 +99/-0 — tests/test_git_prs_coverage.py
pr-16 chore(split): review test-score-pr-refs-16 +99/-0 — tests/test_score_pr_refs.py
pr-17 chore(split): review test-score-pr-comment-17 +84/-0 — tests/test_score_pr_comment.py
pr-18 chore(split): review-slice-18 +379/-21 — pr_split/config.py, pr_split/diff_ops/__init__.py, pr_split/exceptions.py, pr_split/logs.py, pr_split/planner/chunker.py, pr_split/planner/client.py, pr_split/planner/partitioning.py, pr_split/planner/prompts.py, pr_split/planner/validator.py, tests/conftest.py, tests/network_guards.py, tests/test_chunker.py, tests/test_chunker_exhaustive.py, tests/test_client.py, tests/test_config.py, tests/test_exceptions.py, tests/test_partitioning.py, tests/test_prompts.py, tests/test_score_pr_events.py, tests/test_validator.py
pr-19 chore(split): review score-pr-19 +50/-9 — scripts/score_pr.py
pr-20 chore(split): review README-20 +15/-6 — README.md
pr-21 chore(split): review action-21 +11/-3 — action.yml
pr-22 chore(split): review test-types-defs-22 +1/-0 — tests/test_types_defs.py

Run pr-split split locally to create these sub-PRs, or adjust --max-loc to change the target size.

@vitali87
vitali87 merged commit 8ca9e3b into main Sep 27, 2026
4 checks passed
vitali87 pushed a commit that referenced this pull request Sep 28, 2026
Under the default orthogonal priority, keeping two different files in one
group cost 400 per file pair while opening a group cost 250, so the
optimum was always one file per group: 21 PRs, several of 3-6 lines, for
this repo's 1,698-line PR #10. Only unrelated files (no shared directory,
symbols or test pairing) now pay that penalty; related ones get their
affinity reward, so they share a PR when it fits.

The model is also far smaller and starts from a good plan:
- one variable per scored unit pair with linear bounds, instead of one
  per pair per group slot (n^2 variables instead of n^3);
- group slots bounded by the graph plan's size plus a margin, instead of
  one per unit;
- the graph plan seeds the search as a hint;
- groups are numbered in diff order, not by arbitrary solver slot.

On PR #10, cp_sat now plans 8 groups, down from 21, and proves the plan
optimal in about 5 seconds; before, it stopped at its 15-second limit.
On the 4,187-line merge #243 it plans 13 groups without tiny ones (the
graph planner makes 24, 11 of them tiny), scoring 300 on pr-split's plan
objective against the graph plan's 387. That diff still reaches the time
limit without proving optimality, and runs of up to 60 seconds give the
same plan. After the standalone pass, every sub-PR of both splits passes
its own test suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rnEEjXWDBdePRhKMmZr1w
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