diff --git a/code_puppy/command_line/core_commands.py b/code_puppy/command_line/core_commands.py index 0608baf89..0927c99cb 100644 --- a/code_puppy/command_line/core_commands.py +++ b/code_puppy/command_line/core_commands.py @@ -55,7 +55,7 @@ def handle_cd_command(command: str) -> bool: # Use shlex.split to handle quoted paths properly import shlex - from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning + from code_puppy.messaging import emit_error, emit_info, emit_success try: tokens = shlex.split(command) @@ -77,20 +77,19 @@ def handle_cd_command(command: str) -> bool: if os.path.isdir(target): os.chdir(target) emit_success(f"Changed directory to: {target}") - # Reload the agent so the system prompt and project-local - # AGENT.md rules reflect the new working directory. Without - # this, the LLM keeps receiving stale path information for the - # remainder of the session (the PydanticAgent instructions are - # baked in at construction time and never refreshed otherwise). + + # Reload the agent to pick up new working directory context + # This ensures AGENTS.md is re-read and system prompt is updated try: from code_puppy.agents.agent_manager import get_current_agent + # reload_code_generation_agent() invalidates cached rules + # and rebuilds prompt/context from the new cwd get_current_agent().reload_code_generation_agent() + emit_info("Agent context updated for new directory") except Exception as e: - emit_warning( - f"Directory changed, but agent reload failed: {e}. " - "You may need to run /agent or /model to force a refresh." - ) + # Non-fatal: directory change succeeded even if reload failed + emit_error(f"Could not reload agent context: {e}") else: emit_error(f"Not a directory: {dirname}") return True diff --git a/tests/agents/test_base_agent_full_coverage.py b/tests/agents/test_base_agent_full_coverage.py index 4165df12e..e33cfa07e 100644 --- a/tests/agents/test_base_agent_full_coverage.py +++ b/tests/agents/test_base_agent_full_coverage.py @@ -2129,9 +2129,11 @@ def test_loads_from_project_dir(self, agent, tmp_path): patch("code_puppy.config.CONFIG_DIR", str(tmp_path / "nonexistent")), patch( "pathlib.Path.exists", - side_effect=lambda self: str(self) == str(rules_file) - or str(self).endswith("AGENTS.md") - and "nonexistent" not in str(self), + side_effect=lambda self: ( + str(self) == str(rules_file) + or str(self).endswith("AGENTS.md") + and "nonexistent" not in str(self) + ), ), ): # Complex to test due to pathlib patching, just test cached path diff --git a/tests/command_line/test_core_commands_extended.py b/tests/command_line/test_core_commands_extended.py index f16e097f3..f3b1166f5 100644 --- a/tests/command_line/test_core_commands_extended.py +++ b/tests/command_line/test_core_commands_extended.py @@ -134,6 +134,61 @@ def test_cd_listing_with_permission_error(self): args, kwargs = mock_error.call_args assert "Access denied" in args[0] + def test_cd_reloads_agent_context(self): + """Test that /cd reloads agent to pick up new directory context (AGENTS.md, etc).""" + mock_agent = MagicMock() + + with patch("code_puppy.messaging.emit_success"): + with patch("code_puppy.messaging.emit_info") as mock_emit_info: + with patch("os.path.expanduser", side_effect=lambda x: x): + with patch("os.path.isabs", return_value=True): + with patch("os.path.isdir", return_value=True): + with patch("os.chdir"): + with patch( + "code_puppy.agents.agent_manager.get_current_agent", + return_value=mock_agent, + ): + result = handle_cd_command("/cd /new/dir") + + # Verify directory changed + assert result is True + + # Verify agent was reloaded (public behavior) + mock_agent.reload_code_generation_agent.assert_called_once() + + # Verify context refresh message was emitted + mock_emit_info.assert_called_once_with( + "Agent context updated for new directory" + ) + + def test_cd_handles_agent_reload_failure_gracefully(self): + """Test that /cd continues even if agent reload fails.""" + mock_agent = MagicMock() + mock_agent.reload_code_generation_agent.side_effect = Exception("Reload failed") + + with patch("code_puppy.messaging.emit_success"): + with patch("code_puppy.messaging.emit_error") as mock_error: + with patch("os.path.expanduser", side_effect=lambda x: x): + with patch("os.path.isabs", return_value=True): + with patch("os.path.isdir", return_value=True): + with patch("os.chdir"): + with patch( + "code_puppy.agents.agent_manager.get_current_agent", + return_value=mock_agent, + ): + result = handle_cd_command("/cd /new/dir") + + # Directory change should still succeed + assert result is True + + # Error should be emitted about reload failure + mock_error.assert_called_once() + error_msg = mock_error.call_args[0][0] + assert error_msg.startswith( + "Could not reload agent context:" + ) + assert "Reload failed" in error_msg + def test_cd_with_nonexistent_parent(self): """Test cd command with path containing nonexistent parent directories.""" with patch("code_puppy.messaging.emit_error") as mock_error: diff --git a/tests/command_line/test_model_settings_menu_coverage.py b/tests/command_line/test_model_settings_menu_coverage.py index 97e257f24..c96a224c8 100644 --- a/tests/command_line/test_model_settings_menu_coverage.py +++ b/tests/command_line/test_model_settings_menu_coverage.py @@ -151,10 +151,13 @@ def test_get_supported_settings(self, mock_supports): def test_load_model_settings_with_openai( self, mock_supports, mock_get_all, mock_effort, mock_verb ): - mock_supports.side_effect = lambda m, s: s in ( - "temperature", - "reasoning_effort", - "verbosity", + mock_supports.side_effect = lambda m, s: ( + s + in ( + "temperature", + "reasoning_effort", + "verbosity", + ) ) menu = _make_menu() menu._load_model_settings("gpt-5") diff --git a/tests/test_command_handler.py b/tests/test_command_handler.py index ed7459daa..dd40715d4 100644 --- a/tests/test_command_handler.py +++ b/tests/test_command_handler.py @@ -90,7 +90,7 @@ def test_cd_valid_change_reload_failure_is_nonfatal(): """A reload failure after /cd must not abort the directory change.""" mocks = setup_messaging_mocks() mock_emit_success = mocks["emit_success"].start() - mock_emit_warning = mocks["emit_warning"].start() + mock_emit_error = mocks["emit_error"].start() try: mock_agent = MagicMock() @@ -111,14 +111,14 @@ def test_cd_valid_change_reload_failure_is_nonfatal(): mock_chdir.assert_called_once_with("/some/dir") mock_emit_success.assert_called_once_with("Changed directory to: /some/dir") mock_agent.reload_code_generation_agent.assert_called_once() - # Reload failure should emit a warning, not silently pass - mock_emit_warning.assert_called_once() - warning_msg = str(mock_emit_warning.call_args) - assert "agent reload failed" in warning_msg - assert "boom" in warning_msg + # Reload failure should emit an error, not silently pass + mock_emit_error.assert_called_once() + error_msg = mock_emit_error.call_args[0][0] + assert error_msg.startswith("Could not reload agent context:") + assert "boom" in error_msg finally: mocks["emit_success"].stop() - mocks["emit_warning"].stop() + mocks["emit_error"].stop() def test_cd_invalid_directory():