Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pr_split/diff_ops/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ def stats(self) -> DiffStats:
is_deleted=pf.is_removed_file,
is_renamed=pf.is_rename,
hunk_count=len(pf),
hunk_indices=list(range(len(pf))),
)
)
return DiffStats(
Expand Down
1 change: 1 addition & 0 deletions pr_split/planner/chunker.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def build_chunk_stats_from_hunks(parsed_diff: ParsedDiff, hunk_refs: list[HunkRe
is_deleted=pf.is_removed_file,
is_renamed=pf.is_rename,
hunk_count=len(indices),
hunk_indices=sorted(indices),
)
)
return DiffStats(
Expand Down
12 changes: 11 additions & 1 deletion pr_split/planner/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,17 @@ def _format_file_summary(diff_stats: DiffStats) -> str:
]
flag_str = f" [{', '.join(flags)}]" if flags else ""
hunk_count = fs["hunk_count"]
idx_range = f"indices 0..{hunk_count - 1}" if hunk_count > 0 else "no hunks"
indices = fs["hunk_indices"]
# In chunked mode a file's hunks in this chunk keep their per-file
# global indices, which need not start at 0 or be contiguous; list
# them explicitly so the model does not re-use indices from earlier
# chunks.
if not indices:
idx_range = "no hunks"
elif indices == list(range(len(indices))):
idx_range = f"indices 0..{indices[-1]}"
else:
idx_range = f"indices {', '.join(str(i) for i in indices)} (this chunk only)"
lines.append(
f" {fs['path']}: +{fs['added']}/-{fs['removed']}"
f" ({hunk_count} hunks, {idx_range}){flag_str}"
Expand Down
1 change: 1 addition & 0 deletions pr_split/types_defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class FileSummary(TypedDict):
is_deleted: bool
is_renamed: bool
hunk_count: int
hunk_indices: list[int]


class ForkPRInfo(TypedDict):
Expand Down
19 changes: 19 additions & 0 deletions tests/test_chunker.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,22 @@ def test_oversized_hunk_is_pr_split_error_for_both_strategies(self) -> None:
for strategy in (ChunkStrategy.GREEDY, ChunkStrategy.DYNAMIC_PROGRAMMING):
with pytest.raises(PRSplitError, match=r"a\.py\[0\].*exceeds budget 100"):
chunk_hunks(refs, 100, strategy)


class TestChunkStatsKeepGlobalIndices:
def test_second_chunk_summary_uses_per_file_global_indices(self) -> None:
from pr_split.diff_ops.parser import parse_diff
from pr_split.planner.chunker import build_chunk_stats_from_hunks, build_hunk_sequence
from pr_split.planner.prompts import build_chunk_continuation_prompt

diff = "diff --git a/f.py b/f.py\n--- a/f.py\n+++ b/f.py\n" + "".join(
f"@@ -{10 * i + 1},1 +{10 * i + 1},2 @@\n x\n+y{i}\n" for i in range(4)
)
parsed = parse_diff(diff)
refs = build_hunk_sequence(parsed, 0.25)
second_chunk = refs[2:]
stats = build_chunk_stats_from_hunks(parsed, second_chunk)
assert stats["file_summaries"][0]["hunk_indices"] == [2, 3]
prompt = build_chunk_continuation_prompt(stats, "diff", 2, 2, "catalog")
assert "indices 2, 3 (this chunk only)" in prompt
assert "indices 0..1" not in prompt
28 changes: 28 additions & 0 deletions tests/test_prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def _make_diff_stats() -> DiffStats:
is_deleted=False,
is_renamed=False,
hunk_count=2,
hunk_indices=[0, 1],
),
FileSummary(
path="bar.py",
Expand All @@ -36,6 +37,7 @@ def _make_diff_stats() -> DiffStats:
is_deleted=False,
is_renamed=False,
hunk_count=1,
hunk_indices=[0],
),
],
)
Expand Down Expand Up @@ -101,6 +103,7 @@ def test_zero_hunk_file(self) -> None:
is_deleted=False,
is_renamed=False,
hunk_count=0,
hunk_indices=[],
),
],
)
Expand Down Expand Up @@ -130,3 +133,28 @@ class TestBuildSystemPromptExtended:
def test_returns_nonempty_string(self) -> None:
result = build_system_prompt(Priority.ORTHOGONAL, 400)
assert len(result) > 100


class TestChunkSummaryIndices:
def test_later_chunk_lists_global_indices(self) -> None:
stats = DiffStats(
total_files=1,
total_added=1,
total_removed=0,
total_loc=1,
file_summaries=[
FileSummary(
path="f.py",
added=1,
removed=0,
is_new=False,
is_deleted=False,
is_renamed=False,
hunk_count=2,
hunk_indices=[1, 3],
)
],
)
result = build_user_prompt(stats, "diff")
assert "indices 1, 3 (this chunk only)" in result
assert "indices 0..1" not in result
1 change: 1 addition & 0 deletions tests/test_types_defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def test_dict_access(self) -> None:
"is_deleted": False,
"is_renamed": False,
"hunk_count": 2,
"hunk_indices": [0, 1],
}
assert fs["path"] == "test.py"
assert fs["added"] == 10
Expand Down
Loading