diff --git a/aider/commands.py b/aider/commands.py index a276b66cb6a..927ec3215c6 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -1406,6 +1406,29 @@ def cmd_editor(self, initial_content=""): if user_input.strip(): self.io.set_placeholder(user_input.rstrip()) + def cmd_repeat(self, args): + "Repeat a prompt multiple times, using each assistant response as context for the next prompt" + + # Parse args into count and message + try: + count_str, *message_parts = args.strip().split(maxsplit=1) + count = int(count_str) + if count < 1: + raise ValueError("Count must be positive") + message = message_parts[0] if message_parts else "" + except (ValueError, IndexError) as e: + self.io.tool_error("Usage: /repeat N 'message' - where N is a positive number") + return + + if not message: + self.io.tool_error("Please provide a message to repeat") + return + + # Run the prompt multiple times + for i in range(count): + self.io.tool_output(f"\nRepeat {i+1}/{count}: {message}") + self.coder.run(message) + def cmd_copy_context(self, args=None): """Copy the current chat context as markdown, suitable to paste into a web UI""" diff --git a/tests/basic/test_commands.py b/tests/basic/test_commands.py index a234c9b1d9b..48c43a74367 100644 --- a/tests/basic/test_commands.py +++ b/tests/basic/test_commands.py @@ -1722,6 +1722,66 @@ def test_cmd_reset(self): del coder del commands + def test_cmd_repeat_happy_path(self): + # Initialize the Commands and InputOutput objects + io = InputOutput(pretty=False, fancy_input=False, yes=True) + coder = Coder.create(self.GPT35, None, io) + commands = Commands(io, coder) + + # Mock coder.run to track calls + with mock.patch.object(coder, 'run') as mock_run: + commands.cmd_repeat("3 fix the tests") + + # Verify coder.run was called 3 times with the same message + self.assertEqual(mock_run.call_count, 3) + mock_run.assert_has_calls([ + mock.call("fix the tests"), + mock.call("fix the tests"), + mock.call("fix the tests") + ]) + + def test_cmd_repeat_invalid_count(self): + io = InputOutput(pretty=False, fancy_input=False, yes=True) + coder = Coder.create(self.GPT35, None, io) + commands = Commands(io, coder) + + # Test invalid inputs + invalid_inputs = [ + "abc fix tests", # non-numeric count + "-1 fix tests", # negative count + "0 fix tests", # zero count + "", # empty input + ] + + with mock.patch.object(io, 'tool_error') as mock_error: + for invalid_input in invalid_inputs: + commands.cmd_repeat(invalid_input) + mock_error.assert_called_with("Usage: /repeat N 'message' - where N is a positive number") + mock_error.reset_mock() + + def test_cmd_repeat_no_message(self): + io = InputOutput(pretty=False, fancy_input=False, yes=True) + coder = Coder.create(self.GPT35, None, io) + commands = Commands(io, coder) + + with mock.patch.object(io, 'tool_error') as mock_error: + commands.cmd_repeat("3") + mock_error.assert_called_with("Please provide a message to repeat") + + def test_cmd_repeat_with_quoted_message(self): + io = InputOutput(pretty=False, fancy_input=False, yes=True) + coder = Coder.create(self.GPT35, None, io) + commands = Commands(io, coder) + + with mock.patch.object(coder, 'run') as mock_run: + commands.cmd_repeat('2 "fix the tests please"') + + self.assertEqual(mock_run.call_count, 2) + mock_run.assert_has_calls([ + mock.call("fix the tests please"), + mock.call("fix the tests please") + ]) + def test_cmd_load_with_switch_coder(self): with GitTemporaryDirectory() as repo_dir: io = InputOutput(pretty=False, fancy_input=False, yes=True)