Skip to content
Open
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
16 changes: 7 additions & 9 deletions src/kimi_cli/tools/file/replace.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,14 @@ async def __call__(self, params: Params) -> ToolReturnValue:
original_content = content
edits = [params.edit] if isinstance(params.edit, Edit) else params.edit

# Apply all edits
# Apply all edits and count replacements against the content as it
# exists before each edit (fixes under-counting for chained edits).
total_replacements = 0
for edit in edits:
if edit.replace_all:
total_replacements += content.count(edit.old)
else:
total_replacements += 1 if edit.old in content else 0
content = self._apply_edit(content, edit)

# Check if any changes were made
Expand Down Expand Up @@ -169,14 +175,6 @@ async def __call__(self, params: Params) -> ToolReturnValue:
# Write the modified content back to the file
await p.write_text(content, errors="replace")

# Count changes for success message
total_replacements = 0
for edit in edits:
if edit.replace_all:
total_replacements += original_content.count(edit.old)
else:
total_replacements += 1 if edit.old in original_content else 0

return ToolReturnValue(
is_error=False,
output="",
Expand Down
23 changes: 23 additions & 0 deletions tests/tools/test_str_replace_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,29 @@ async def test_replace_multiple_edits(
assert await file_path.read_text() == "Hi world! See you world!"


async def test_replace_chained_edits_count(
str_replace_file_tool: StrReplaceFile, temp_work_dir: KaosPath
):
"""Regression test for #2526: chained edits must count against intermediate content."""
file_path = temp_work_dir / "test.txt"
await file_path.write_text("hello world")

result = await str_replace_file_tool(
Params(
path=str(file_path),
edit=[
Edit(old="hello", new="goodbye"),
Edit(old="goodbye", new="farewell"),
],
)
)

assert not result.is_error
assert await file_path.read_text() == "farewell world"
# The bug reported 1 replacement; the fix should report 2.
assert "2 total replacement" in result.message


async def test_replace_multiline_content(
str_replace_file_tool: StrReplaceFile, temp_work_dir: KaosPath
):
Expand Down
Loading