From 058c778886d75352e9e02bf575414db2334f4a8b Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Fri, 31 Oct 2025 16:17:03 -0400 Subject: [PATCH 1/6] x --- libs/deepagents-cli/tests/test_cli.py | 26 ++ libs/deepagents-cli/tests/test_commands.py | 174 ++++++++++++ libs/deepagents-cli/tests/test_config.py | 141 +++++++++ libs/deepagents-cli/tests/test_input_pure.py | 131 +++++++++ libs/deepagents-cli/tests/test_token_utils.py | 146 ++++++++++ libs/deepagents-cli/tests/test_ui_pure.py | 267 ++++++++++++++++++ 6 files changed, 885 insertions(+) create mode 100644 libs/deepagents-cli/tests/test_cli.py create mode 100644 libs/deepagents-cli/tests/test_commands.py create mode 100644 libs/deepagents-cli/tests/test_config.py create mode 100644 libs/deepagents-cli/tests/test_input_pure.py create mode 100644 libs/deepagents-cli/tests/test_token_utils.py create mode 100644 libs/deepagents-cli/tests/test_ui_pure.py diff --git a/libs/deepagents-cli/tests/test_cli.py b/libs/deepagents-cli/tests/test_cli.py new file mode 100644 index 00000000..07eacd43 --- /dev/null +++ b/libs/deepagents-cli/tests/test_cli.py @@ -0,0 +1,26 @@ +"""Tests for CLI entry points.""" + +from io import StringIO +from unittest.mock import patch + +from deepagents_cli.cli import cli_main + + +def test_cli_main_prints_message(): + """Test that cli_main prints the expected message.""" + output = StringIO() + with patch("sys.stdout", output): + cli_main() + + assert "I'm alive!" in output.getvalue() + + +def test_cli_main_callable(): + """Test that cli_main is callable without errors.""" + # This should not raise any exceptions + output = StringIO() + with patch("sys.stdout", output): + cli_main() + + # Verify it printed something + assert output.getvalue().strip() != "" diff --git a/libs/deepagents-cli/tests/test_commands.py b/libs/deepagents-cli/tests/test_commands.py new file mode 100644 index 00000000..e8476fec --- /dev/null +++ b/libs/deepagents-cli/tests/test_commands.py @@ -0,0 +1,174 @@ +"""Tests for command handlers.""" + +import subprocess +from unittest.mock import Mock, patch + +import pytest + +from deepagents_cli.commands import execute_bash_command, handle_command + + +@pytest.fixture +def mock_agent(): + """Create a mock agent with checkpointer.""" + agent = Mock() + agent.checkpointer = Mock() + return agent + + +@pytest.fixture +def mock_token_tracker(): + """Create a mock token tracker.""" + tracker = Mock() + tracker.reset = Mock() + tracker.display_session = Mock() + return tracker + + +class TestHandleCommand: + """Tests for handle_command function.""" + + def test_quit_command(self, mock_agent, mock_token_tracker): + """Test that /quit returns 'exit'.""" + result = handle_command("/quit", mock_agent, mock_token_tracker) + assert result == "exit" + + def test_exit_command(self, mock_agent, mock_token_tracker): + """Test that /exit returns 'exit'.""" + result = handle_command("/exit", mock_agent, mock_token_tracker) + assert result == "exit" + + def test_q_command(self, mock_agent, mock_token_tracker): + """Test that /q returns 'exit'.""" + result = handle_command("/q", mock_agent, mock_token_tracker) + assert result == "exit" + + def test_quit_command_case_insensitive(self, mock_agent, mock_token_tracker): + """Test that quit commands work regardless of case.""" + assert handle_command("/QUIT", mock_agent, mock_token_tracker) == "exit" + assert handle_command("/Exit", mock_agent, mock_token_tracker) == "exit" + + def test_clear_command(self, mock_agent, mock_token_tracker): + """Test that /clear resets state and returns True.""" + from langgraph.checkpoint.memory import InMemorySaver + + result = handle_command("/clear", mock_agent, mock_token_tracker) + + assert result is True + # Verify checkpointer was reset + assert isinstance(mock_agent.checkpointer, InMemorySaver) + # Verify token tracker was reset + mock_token_tracker.reset.assert_called_once() + + def test_help_command(self, mock_agent, mock_token_tracker): + """Test that /help returns True.""" + with patch("deepagents_cli.commands.show_interactive_help"): + result = handle_command("/help", mock_agent, mock_token_tracker) + assert result is True + + def test_tokens_command(self, mock_agent, mock_token_tracker): + """Test that /tokens displays session and returns True.""" + result = handle_command("/tokens", mock_agent, mock_token_tracker) + + assert result is True + mock_token_tracker.display_session.assert_called_once() + + def test_unknown_command(self, mock_agent, mock_token_tracker): + """Test that unknown command returns True.""" + result = handle_command("/unknown", mock_agent, mock_token_tracker) + assert result is True + + def test_command_with_leading_slash(self, mock_agent, mock_token_tracker): + """Test commands work with leading slash.""" + result = handle_command("/quit", mock_agent, mock_token_tracker) + assert result == "exit" + + def test_command_without_leading_slash(self, mock_agent, mock_token_tracker): + """Test commands work without leading slash.""" + result = handle_command("quit", mock_agent, mock_token_tracker) + assert result == "exit" + + def test_command_with_whitespace(self, mock_agent, mock_token_tracker): + """Test commands work with surrounding whitespace.""" + result = handle_command(" /quit ", mock_agent, mock_token_tracker) + assert result == "exit" + + +class TestExecuteBashCommand: + """Tests for execute_bash_command function.""" + + def test_execute_simple_command(self): + """Test executing a simple bash command.""" + with patch("subprocess.run") as mock_run: + mock_run.return_value = subprocess.CompletedProcess( + args=["echo", "hello"], returncode=0, stdout="hello\n", stderr="" + ) + + result = execute_bash_command("!echo hello") + assert result is True + mock_run.assert_called_once() + + def test_execute_empty_command(self): + """Test that empty command is handled.""" + result = execute_bash_command("!") + assert result is True + + def test_execute_command_with_stderr(self): + """Test command that produces stderr.""" + with patch("subprocess.run") as mock_run: + mock_run.return_value = subprocess.CompletedProcess( + args=["ls", "nonexistent"], returncode=1, stdout="", stderr="ls: cannot access" + ) + + result = execute_bash_command("!ls nonexistent") + assert result is True + + def test_execute_command_timeout(self): + """Test command timeout handling.""" + with patch("subprocess.run") as mock_run: + mock_run.side_effect = subprocess.TimeoutExpired(cmd="sleep 100", timeout=30) + + result = execute_bash_command("!sleep 100") + assert result is True + + def test_execute_command_exception(self): + """Test command execution exception handling.""" + with patch("subprocess.run") as mock_run: + mock_run.side_effect = Exception("Test error") + + result = execute_bash_command("!invalid") + assert result is True + + def test_execute_command_strips_exclamation(self): + """Test that leading ! is stripped from command.""" + with patch("subprocess.run") as mock_run: + mock_run.return_value = subprocess.CompletedProcess( + args=["pwd"], returncode=0, stdout="/home/user\n", stderr="" + ) + + execute_bash_command("!pwd") + + # Check that the command was called with shell=True and the right command + call_args = mock_run.call_args + assert call_args[0][0] == "pwd" # Command without ! + assert call_args[1]["shell"] is True + + def test_execute_command_nonzero_exit_code(self): + """Test command with non-zero exit code.""" + with patch("subprocess.run") as mock_run: + mock_run.return_value = subprocess.CompletedProcess( + args=["false"], returncode=1, stdout="", stderr="" + ) + + result = execute_bash_command("!false") + assert result is True + + def test_execute_command_with_whitespace(self): + """Test command with leading/trailing whitespace.""" + with patch("subprocess.run") as mock_run: + mock_run.return_value = subprocess.CompletedProcess( + args=["echo", "test"], returncode=0, stdout="test\n", stderr="" + ) + + result = execute_bash_command(" !echo test ") + assert result is True diff --git a/libs/deepagents-cli/tests/test_config.py b/libs/deepagents-cli/tests/test_config.py new file mode 100644 index 00000000..0f78daa0 --- /dev/null +++ b/libs/deepagents-cli/tests/test_config.py @@ -0,0 +1,141 @@ +"""Tests for configuration and utilities.""" + +import os +from pathlib import Path +from unittest.mock import patch + +import pytest + +from deepagents_cli.config import ( + COLORS, + COMMANDS, + COMMON_BASH_COMMANDS, + MAX_ARG_LENGTH, + SessionState, + create_model, + get_default_coding_instructions, +) + + +def test_session_state_initialization(): + """Test SessionState initializes with correct defaults.""" + state = SessionState() + assert state.auto_approve is False + + state_with_approve = SessionState(auto_approve=True) + assert state_with_approve.auto_approve is True + + +def test_session_state_toggle_auto_approve(): + """Test SessionState toggle functionality.""" + state = SessionState(auto_approve=False) + + # Toggle from False to True + result = state.toggle_auto_approve() + assert result is True + assert state.auto_approve is True + + # Toggle from True to False + result = state.toggle_auto_approve() + assert result is False + assert state.auto_approve is False + + +def test_get_default_coding_instructions_returns_content(): + """Test that get_default_coding_instructions reads and returns content.""" + instructions = get_default_coding_instructions() + + # Should return non-empty string + assert isinstance(instructions, str) + assert len(instructions) > 0 + + +def test_get_default_coding_instructions_file_exists(): + """Test that the default agent prompt file exists.""" + from deepagents_cli import config + + prompt_path = Path(config.__file__).parent / "default_agent_prompt.md" + assert prompt_path.exists() + assert prompt_path.is_file() + + +def test_constants_are_defined(): + """Test that important constants are defined correctly.""" + # Color constants + assert isinstance(COLORS, dict) + assert "primary" in COLORS + assert "agent" in COLORS + assert "user" in COLORS + + # Command constants + assert isinstance(COMMANDS, dict) + assert "clear" in COMMANDS + assert "help" in COMMANDS + assert "quit" in COMMANDS + + # Bash commands + assert isinstance(COMMON_BASH_COMMANDS, dict) + assert "ls" in COMMON_BASH_COMMANDS + assert "cd" in COMMON_BASH_COMMANDS + + # Max arg length + assert isinstance(MAX_ARG_LENGTH, int) + assert MAX_ARG_LENGTH > 0 + + +def test_create_model_no_api_keys(): + """Test that create_model exits when no API keys are configured.""" + with patch.dict(os.environ, {}, clear=True): + # Should exit with code 1 + with pytest.raises(SystemExit) as exc_info: + create_model() + assert exc_info.value.code == 1 + + +def test_create_model_with_openai_key(): + """Test that create_model returns OpenAI model when key is set.""" + with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=True): + model = create_model() + # Check that we got a ChatOpenAI instance + assert model.__class__.__name__ == "ChatOpenAI" + + +def test_create_model_with_anthropic_key(): + """Test that create_model returns Anthropic model when key is set.""" + with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-key"}, clear=True): + model = create_model() + # Check that we got a ChatAnthropic instance + assert model.__class__.__name__ == "ChatAnthropic" + + +def test_create_model_prefers_openai(): + """Test that create_model prefers OpenAI when both keys are set.""" + with patch.dict( + os.environ, + {"OPENAI_API_KEY": "openai-key", "ANTHROPIC_API_KEY": "anthropic-key"}, + clear=True, + ): + model = create_model() + # Should prefer OpenAI + assert model.__class__.__name__ == "ChatOpenAI" + + +def test_create_model_custom_model_name_openai(): + """Test that create_model respects custom OpenAI model name.""" + with patch.dict( + os.environ, {"OPENAI_API_KEY": "test-key", "OPENAI_MODEL": "gpt-4o"}, clear=True + ): + model = create_model() + assert model.model_name == "gpt-4o" + + +def test_create_model_custom_model_name_anthropic(): + """Test that create_model respects custom Anthropic model name.""" + with patch.dict( + os.environ, + {"ANTHROPIC_API_KEY": "test-key", "ANTHROPIC_MODEL": "claude-3-opus-20240229"}, + clear=True, + ): + model = create_model() + # ChatAnthropic uses 'model' attribute, not 'model_name' + assert model.model == "claude-3-opus-20240229" diff --git a/libs/deepagents-cli/tests/test_input_pure.py b/libs/deepagents-cli/tests/test_input_pure.py new file mode 100644 index 00000000..b1883470 --- /dev/null +++ b/libs/deepagents-cli/tests/test_input_pure.py @@ -0,0 +1,131 @@ +"""Tests for input parsing functions (no mocks needed).""" + +from deepagents_cli.input import parse_file_mentions + + +class TestParseFileMentions: + """Tests for parse_file_mentions function.""" + + def test_no_mentions(self): + """Test text with no @ mentions.""" + text, files = parse_file_mentions("hello world") + assert text == "hello world" + assert files == [] + + def test_single_file_mention(self, tmp_path): + """Test single @file mention.""" + test_file = tmp_path / "test.txt" + test_file.write_text("content") + + text, files = parse_file_mentions(f"Read @{test_file}") + assert text == f"Read @{test_file}" + assert len(files) == 1 + assert files[0] == test_file.resolve() + + def test_multiple_file_mentions(self, tmp_path): + """Test multiple @file mentions.""" + file1 = tmp_path / "file1.txt" + file2 = tmp_path / "file2.txt" + file1.write_text("content1") + file2.write_text("content2") + + text, files = parse_file_mentions(f"Read @{file1} and @{file2}") + assert len(files) == 2 + assert file1.resolve() in files + assert file2.resolve() in files + + def test_nonexistent_file(self, tmp_path): + """Test @mention of nonexistent file.""" + fake_file = tmp_path / "nonexistent.txt" + text, files = parse_file_mentions(f"Read @{fake_file}") + assert files == [] # Nonexistent files are filtered out + + def test_file_with_spaces_escaped(self, tmp_path): + """Test file with escaped spaces.""" + file_with_space = tmp_path / "my file.txt" + file_with_space.write_text("content") + + # Use escaped space + text, files = parse_file_mentions(f"Read @{str(file_with_space).replace(' ', r'\ ')}") + assert len(files) == 1 + assert files[0].name == "my file.txt" + + def test_relative_path(self, tmp_path, monkeypatch): + """Test relative path resolution.""" + # Change to tmp_path + monkeypatch.chdir(tmp_path) + + test_file = tmp_path / "test.txt" + test_file.write_text("content") + + # Use relative path + text, files = parse_file_mentions("Read @test.txt") + assert len(files) == 1 + assert files[0].name == "test.txt" + + def test_absolute_path(self, tmp_path): + """Test absolute path.""" + test_file = tmp_path / "test.txt" + test_file.write_text("content") + + text, files = parse_file_mentions(f"Read @{test_file.absolute()}") + assert len(files) == 1 + assert files[0] == test_file.resolve() + + def test_expanduser_tilde(self): + """Test ~ expansion in paths uses user's home directory.""" + # Create a test file in actual home directory (if accessible) + # This is a lightweight test - just verify the function handles ~ + # We can't easily mock expanduser, so we just test it doesn't crash + text, files = parse_file_mentions("Read @~/nonexistent_test_file_12345.txt") + # File won't exist, so should return empty list + assert files == [] + # But text should be preserved + assert text == "Read @~/nonexistent_test_file_12345.txt" + + def test_directory_not_included(self, tmp_path): + """Test that directories are not included (only files).""" + test_dir = tmp_path / "testdir" + test_dir.mkdir() + + text, files = parse_file_mentions(f"Read @{test_dir}") + assert files == [] # Directories should not be included + + def test_multiple_at_symbols(self, tmp_path): + """Test text with multiple @ symbols (not all file mentions).""" + test_file = tmp_path / "file.txt" + test_file.write_text("content") + + text, files = parse_file_mentions(f"Email me@example.com and read @{test_file}") + # Should parse both @ mentions, but only the valid file is included + assert len(files) == 1 + assert files[0].name == "file.txt" + + def test_at_symbol_at_end(self, tmp_path): + """Test @ symbol at end of text.""" + text, files = parse_file_mentions("Send to user@") + assert files == [] + + def test_empty_string(self): + """Test empty string input.""" + text, files = parse_file_mentions("") + assert text == "" + assert files == [] + + def test_text_preserved(self, tmp_path): + """Test that original text is returned unchanged.""" + test_file = tmp_path / "test.txt" + test_file.write_text("content") + + original = f"Please read @{test_file} carefully" + text, files = parse_file_mentions(original) + assert text == original # Text should be unchanged + + def test_special_characters_in_filename(self, tmp_path): + """Test file with special characters.""" + special_file = tmp_path / "file-name_123.txt" + special_file.write_text("content") + + text, files = parse_file_mentions(f"Read @{special_file}") + assert len(files) == 1 + assert files[0].name == "file-name_123.txt" diff --git a/libs/deepagents-cli/tests/test_token_utils.py b/libs/deepagents-cli/tests/test_token_utils.py new file mode 100644 index 00000000..03e95264 --- /dev/null +++ b/libs/deepagents-cli/tests/test_token_utils.py @@ -0,0 +1,146 @@ +"""Tests for token counting utilities.""" + +from unittest.mock import Mock + +from deepagents_cli.token_utils import calculate_baseline_tokens, get_memory_system_prompt + + +def test_get_memory_system_prompt_returns_string(): + """Test that get_memory_system_prompt returns a formatted string.""" + prompt = get_memory_system_prompt() + + assert isinstance(prompt, str) + assert len(prompt) > 0 + # Should contain the memory path + assert "/memories/" in prompt + + +def test_get_memory_system_prompt_formatting(): + """Test that get_memory_system_prompt properly formats the template.""" + prompt = get_memory_system_prompt() + + # The prompt should be formatted with the memory path + # Check that there are no unformatted template placeholders + assert "{memory_path}" not in prompt + + +def test_calculate_baseline_tokens_with_agent_md(tmp_path): + """Test calculate_baseline_tokens with an agent.md file.""" + # Create a temporary agent directory with agent.md + agent_dir = tmp_path / "agent" + agent_dir.mkdir() + agent_md = agent_dir / "agent.md" + agent_md.write_text("# Test Agent\n\nThis is a test agent.") + + # Create a mock model with token counting + mock_model = Mock() + mock_model.get_num_tokens_from_messages.return_value = 100 + + system_prompt = "You are a helpful assistant." + + tokens = calculate_baseline_tokens(mock_model, agent_dir, system_prompt) + + # Should return the token count from the model + assert tokens == 100 + # Should have called the model's token counting method + mock_model.get_num_tokens_from_messages.assert_called_once() + + +def test_calculate_baseline_tokens_without_agent_md(tmp_path): + """Test calculate_baseline_tokens without an agent.md file.""" + # Create a temporary agent directory without agent.md + agent_dir = tmp_path / "agent" + agent_dir.mkdir() + + # Create a mock model + mock_model = Mock() + mock_model.get_num_tokens_from_messages.return_value = 50 + + system_prompt = "You are a helpful assistant." + + tokens = calculate_baseline_tokens(mock_model, agent_dir, system_prompt) + + # Should still return a token count + assert tokens == 50 + mock_model.get_num_tokens_from_messages.assert_called_once() + + +def test_calculate_baseline_tokens_includes_system_components(tmp_path): + """Test that calculate_baseline_tokens includes all system prompt components.""" + agent_dir = tmp_path / "agent" + agent_dir.mkdir() + agent_md = agent_dir / "agent.md" + agent_md.write_text("Test memory") + + mock_model = Mock() + mock_model.get_num_tokens_from_messages.return_value = 200 + + system_prompt = "Base prompt" + + calculate_baseline_tokens(mock_model, agent_dir, system_prompt) + + # Check that the messages passed to the model include all components + call_args = mock_model.get_num_tokens_from_messages.call_args[0][0] + assert len(call_args) == 1 # Should be one SystemMessage + message_content = call_args[0].content + + # Should include agent memory + assert "" in message_content + assert "Test memory" in message_content + + # Should include base prompt + assert "Base prompt" in message_content + + # Should include memory system prompt + assert "/memories/" in message_content + + +def test_calculate_baseline_tokens_handles_exception(tmp_path): + """Test that calculate_baseline_tokens handles exceptions gracefully.""" + agent_dir = tmp_path / "agent" + agent_dir.mkdir() + + # Create a mock model that raises an exception + mock_model = Mock() + mock_model.get_num_tokens_from_messages.side_effect = Exception("Token counting failed") + + system_prompt = "Test prompt" + + # Should return 0 on exception, not raise + tokens = calculate_baseline_tokens(mock_model, agent_dir, system_prompt) + assert tokens == 0 + + +def test_calculate_baseline_tokens_with_empty_agent_md(tmp_path): + """Test calculate_baseline_tokens with an empty agent.md file.""" + agent_dir = tmp_path / "agent" + agent_dir.mkdir() + agent_md = agent_dir / "agent.md" + agent_md.write_text("") # Empty file + + mock_model = Mock() + mock_model.get_num_tokens_from_messages.return_value = 75 + + system_prompt = "Prompt" + + tokens = calculate_baseline_tokens(mock_model, agent_dir, system_prompt) + + # Should still work with empty agent.md + assert tokens == 75 + + +def test_calculate_baseline_tokens_creates_system_message(tmp_path): + """Test that calculate_baseline_tokens creates a proper SystemMessage.""" + from langchain_core.messages import SystemMessage + + agent_dir = tmp_path / "agent" + agent_dir.mkdir() + + mock_model = Mock() + mock_model.get_num_tokens_from_messages.return_value = 100 + + calculate_baseline_tokens(mock_model, agent_dir, "Test") + + # Verify SystemMessage was created + call_args = mock_model.get_num_tokens_from_messages.call_args[0][0] + assert isinstance(call_args[0], SystemMessage) diff --git a/libs/deepagents-cli/tests/test_ui_pure.py b/libs/deepagents-cli/tests/test_ui_pure.py new file mode 100644 index 00000000..46216524 --- /dev/null +++ b/libs/deepagents-cli/tests/test_ui_pure.py @@ -0,0 +1,267 @@ +"""Tests for pure UI utility functions (no mocks needed).""" + +from deepagents_cli.ui import ( + _format_line_span, + format_tool_display, + format_tool_message_content, + truncate_value, +) + + +class TestTruncateValue: + """Tests for truncate_value function.""" + + def test_truncate_short_string(self): + """Test that short strings are not truncated.""" + result = truncate_value("hello", max_length=10) + assert result == "hello" + + def test_truncate_exact_length(self): + """Test string exactly at max length.""" + result = truncate_value("1234567890", max_length=10) + assert result == "1234567890" + + def test_truncate_long_string(self): + """Test that long strings are truncated with ellipsis.""" + result = truncate_value("1234567890abc", max_length=10) + assert result == "1234567890..." + assert len(result) == 13 # 10 chars + "..." + + def test_truncate_default_max_length(self): + """Test truncate uses default MAX_ARG_LENGTH.""" + from deepagents_cli.config import MAX_ARG_LENGTH + + long_string = "x" * (MAX_ARG_LENGTH + 100) + result = truncate_value(long_string) + assert result.endswith("...") + assert len(result) == MAX_ARG_LENGTH + 3 + + def test_truncate_empty_string(self): + """Test empty string returns empty.""" + result = truncate_value("") + assert result == "" + + def test_truncate_with_special_characters(self): + """Test truncation preserves special characters.""" + result = truncate_value("hello🌟world", max_length=8) + assert result == "hello🌟wo..." + + +class TestFormatToolDisplay: + """Tests for format_tool_display function.""" + + def test_read_file_with_file_path(self): + """Test read_file shows abbreviated path.""" + result = format_tool_display("read_file", {"file_path": "/long/path/to/file.py"}) + assert "read_file" in result + assert "file.py" in result + + def test_write_file_with_path(self): + """Test write_file with 'path' argument.""" + result = format_tool_display("write_file", {"path": "config.yaml"}) + assert "write_file" in result + assert "config.yaml" in result + + def test_edit_file(self): + """Test edit_file formatting.""" + result = format_tool_display("edit_file", {"file_path": "main.py"}) + assert result == "edit_file(main.py)" + + def test_web_search(self): + """Test web_search shows query.""" + result = format_tool_display("web_search", {"query": "how to code in python"}) + assert result == 'web_search("how to code in python")' + + def test_web_search_long_query(self): + """Test web_search truncates long queries.""" + long_query = "x" * 150 + result = format_tool_display("web_search", {"query": long_query}) + assert "..." in result + # Should be truncated to 100 chars + "..." + assert len(result) <= len('web_search("")') + 100 + 3 + 10 + + def test_grep_with_pattern(self): + """Test grep shows search pattern.""" + result = format_tool_display("grep", {"pattern": "def.*main"}) + assert result == 'grep("def.*main")' + + def test_shell_command(self): + """Test shell shows command.""" + result = format_tool_display("shell", {"command": "git status"}) + assert result == 'shell("git status")' + + def test_shell_long_command(self): + """Test shell truncates long commands.""" + long_cmd = "echo " + "x" * 200 + result = format_tool_display("shell", {"command": long_cmd}) + assert "..." in result + + def test_ls_with_path(self): + """Test ls with directory path.""" + result = format_tool_display("ls", {"path": "/home/user"}) + assert "ls(" in result + assert "home" in result or "user" in result + + def test_ls_without_path(self): + """Test ls without path shows empty parens.""" + result = format_tool_display("ls", {}) + assert result == "ls()" + + def test_glob_with_pattern(self): + """Test glob shows pattern.""" + result = format_tool_display("glob", {"pattern": "**/*.py"}) + assert result == 'glob("**/*.py")' + + def test_http_request_get(self): + """Test HTTP request formatting.""" + result = format_tool_display( + "http_request", {"method": "GET", "url": "https://example.com/api"} + ) + assert "http_request" in result + assert "GET" in result + assert "example.com" in result + + def test_http_request_post(self): + """Test HTTP POST request.""" + result = format_tool_display("http_request", {"method": "post", "url": "https://api.test"}) + assert "POST" in result # Should be uppercase + + def test_http_request_long_url(self): + """Test HTTP request with long URL.""" + long_url = "https://example.com/" + "path/" * 50 + result = format_tool_display("http_request", {"method": "GET", "url": long_url}) + assert "..." in result + + def test_task_with_description(self): + """Test task formatting.""" + result = format_tool_display("task", {"description": "Install dependencies"}) + assert result == 'task("Install dependencies")' + + def test_write_todos(self): + """Test write_todos shows count.""" + todos = [{"content": "Task 1"}, {"content": "Task 2"}, {"content": "Task 3"}] + result = format_tool_display("write_todos", {"todos": todos}) + assert result == "write_todos(3 items)" + + def test_write_todos_single_item(self): + """Test write_todos with single item.""" + result = format_tool_display("write_todos", {"todos": [{"content": "Task"}]}) + assert result == "write_todos(1 items)" + + def test_write_todos_empty_list(self): + """Test write_todos with empty list.""" + result = format_tool_display("write_todos", {"todos": []}) + assert result == "write_todos(0 items)" + + def test_unknown_tool_fallback(self): + """Test unknown tool uses fallback formatting.""" + result = format_tool_display("unknown_tool", {"arg1": "value1", "arg2": "value2"}) + assert "unknown_tool" in result + assert "arg1" in result + assert "value1" in result + + def test_tool_with_no_args(self): + """Test tool with no arguments.""" + result = format_tool_display("some_tool", {}) + assert result == "some_tool()" + + def test_read_file_simple_filename(self): + """Test read_file with simple filename (no path).""" + result = format_tool_display("read_file", {"file_path": "README.md"}) + assert result == "read_file(README.md)" + + +class TestFormatToolMessageContent: + """Tests for format_tool_message_content function.""" + + def test_none_content(self): + """Test None returns empty string.""" + result = format_tool_message_content(None) + assert result == "" + + def test_string_content(self): + """Test string content is returned as-is.""" + result = format_tool_message_content("hello world") + assert result == "hello world" + + def test_list_of_strings(self): + """Test list of strings joined with newlines.""" + result = format_tool_message_content(["line1", "line2", "line3"]) + assert result == "line1\nline2\nline3" + + def test_list_with_dicts(self): + """Test list containing dicts converted to JSON.""" + content = [{"key": "value"}, {"foo": "bar"}] + result = format_tool_message_content(content) + assert '{"key": "value"}' in result + assert '{"foo": "bar"}' in result + assert "\n" in result + + def test_list_mixed_types(self): + """Test list with mixed types.""" + content = ["text", {"dict": "value"}, 123] + result = format_tool_message_content(content) + assert "text" in result + assert '"dict"' in result + assert "123" in result + + def test_number_content(self): + """Test number is converted to string.""" + result = format_tool_message_content(42) + assert result == "42" + + def test_dict_content(self): + """Test dict is converted to string.""" + result = format_tool_message_content({"key": "value"}) + assert "key" in result + assert "value" in result + + def test_empty_list(self): + """Test empty list returns empty string.""" + result = format_tool_message_content([]) + assert result == "" + + def test_list_with_none_values(self): + """Test list containing None values.""" + result = format_tool_message_content([None, "text", None]) + # Should handle None gracefully + assert "text" in result + + +class TestFormatLineSpan: + """Tests for _format_line_span function.""" + + def test_both_none(self): + """Test when both start and end are None.""" + result = _format_line_span(None, None) + assert result == "" + + def test_only_start(self): + """Test when only start is provided.""" + result = _format_line_span(10, None) + assert result == "(starting at line 10)" + + def test_only_end(self): + """Test when only end is provided.""" + result = _format_line_span(None, 20) + assert result == "(through line 20)" + + def test_same_line(self): + """Test when start equals end.""" + result = _format_line_span(5, 5) + assert result == "(line 5)" + + def test_line_range(self): + """Test when start and end are different.""" + result = _format_line_span(1, 10) + assert result == "(lines 1-10)" + + def test_large_range(self): + """Test with large line numbers.""" + result = _format_line_span(1000, 2000) + assert result == "(lines 1000-2000)" + + def test_single_digit_lines(self): + """Test with single digit line numbers.""" + result = _format_line_span(1, 9) + assert result == "(lines 1-9)" From d1039fae6b52b70c39804e1b8cfc0530e3de1e83 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Fri, 31 Oct 2025 16:27:15 -0400 Subject: [PATCH 2/6] x --- libs/deepagents-cli/tests/test_cli.py | 13 +-- libs/deepagents-cli/tests/test_commands.py | 73 ++++++---------- libs/deepagents-cli/tests/test_config.py | 48 +++-------- libs/deepagents-cli/tests/test_input_pure.py | 28 +++--- libs/deepagents-cli/tests/test_token_utils.py | 16 ++-- libs/deepagents-cli/tests/test_ui_pure.py | 86 +++++++++---------- 6 files changed, 104 insertions(+), 160 deletions(-) diff --git a/libs/deepagents-cli/tests/test_cli.py b/libs/deepagents-cli/tests/test_cli.py index 07eacd43..8fe2fd92 100644 --- a/libs/deepagents-cli/tests/test_cli.py +++ b/libs/deepagents-cli/tests/test_cli.py @@ -6,21 +6,10 @@ from deepagents_cli.cli import cli_main -def test_cli_main_prints_message(): +def test_cli_main_prints_message() -> None: """Test that cli_main prints the expected message.""" output = StringIO() with patch("sys.stdout", output): cli_main() assert "I'm alive!" in output.getvalue() - - -def test_cli_main_callable(): - """Test that cli_main is callable without errors.""" - # This should not raise any exceptions - output = StringIO() - with patch("sys.stdout", output): - cli_main() - - # Verify it printed something - assert output.getvalue().strip() != "" diff --git a/libs/deepagents-cli/tests/test_commands.py b/libs/deepagents-cli/tests/test_commands.py index e8476fec..49b0814b 100644 --- a/libs/deepagents-cli/tests/test_commands.py +++ b/libs/deepagents-cli/tests/test_commands.py @@ -9,7 +9,7 @@ @pytest.fixture -def mock_agent(): +def mock_agent() -> Mock: """Create a mock agent with checkpointer.""" agent = Mock() agent.checkpointer = Mock() @@ -17,7 +17,7 @@ def mock_agent(): @pytest.fixture -def mock_token_tracker(): +def mock_token_tracker() -> Mock: """Create a mock token tracker.""" tracker = Mock() tracker.reset = Mock() @@ -28,27 +28,13 @@ def mock_token_tracker(): class TestHandleCommand: """Tests for handle_command function.""" - def test_quit_command(self, mock_agent, mock_token_tracker): - """Test that /quit returns 'exit'.""" - result = handle_command("/quit", mock_agent, mock_token_tracker) + @pytest.mark.parametrize("command", ["/quit", "/exit", "/q", "/QUIT", "/Exit", "/Q"]) + def test_exit_commands(self, command: str, mock_agent: Mock, mock_token_tracker: Mock) -> None: + """Test that all exit command variants return 'exit'.""" + result = handle_command(command, mock_agent, mock_token_tracker) assert result == "exit" - def test_exit_command(self, mock_agent, mock_token_tracker): - """Test that /exit returns 'exit'.""" - result = handle_command("/exit", mock_agent, mock_token_tracker) - assert result == "exit" - - def test_q_command(self, mock_agent, mock_token_tracker): - """Test that /q returns 'exit'.""" - result = handle_command("/q", mock_agent, mock_token_tracker) - assert result == "exit" - - def test_quit_command_case_insensitive(self, mock_agent, mock_token_tracker): - """Test that quit commands work regardless of case.""" - assert handle_command("/QUIT", mock_agent, mock_token_tracker) == "exit" - assert handle_command("/Exit", mock_agent, mock_token_tracker) == "exit" - - def test_clear_command(self, mock_agent, mock_token_tracker): + def test_clear_command(self, mock_agent: Mock, mock_token_tracker: Mock) -> None: """Test that /clear resets state and returns True.""" from langgraph.checkpoint.memory import InMemorySaver @@ -60,44 +46,41 @@ def test_clear_command(self, mock_agent, mock_token_tracker): # Verify token tracker was reset mock_token_tracker.reset.assert_called_once() - def test_help_command(self, mock_agent, mock_token_tracker): + def test_help_command(self, mock_agent: Mock, mock_token_tracker: Mock) -> None: """Test that /help returns True.""" with patch("deepagents_cli.commands.show_interactive_help"): result = handle_command("/help", mock_agent, mock_token_tracker) assert result is True - def test_tokens_command(self, mock_agent, mock_token_tracker): + def test_tokens_command(self, mock_agent: Mock, mock_token_tracker: Mock) -> None: """Test that /tokens displays session and returns True.""" result = handle_command("/tokens", mock_agent, mock_token_tracker) assert result is True mock_token_tracker.display_session.assert_called_once() - def test_unknown_command(self, mock_agent, mock_token_tracker): + def test_unknown_command(self, mock_agent: Mock, mock_token_tracker: Mock) -> None: """Test that unknown command returns True.""" result = handle_command("/unknown", mock_agent, mock_token_tracker) assert result is True - def test_command_with_leading_slash(self, mock_agent, mock_token_tracker): - """Test commands work with leading slash.""" - result = handle_command("/quit", mock_agent, mock_token_tracker) - assert result == "exit" - - def test_command_without_leading_slash(self, mock_agent, mock_token_tracker): - """Test commands work without leading slash.""" - result = handle_command("quit", mock_agent, mock_token_tracker) - assert result == "exit" - - def test_command_with_whitespace(self, mock_agent, mock_token_tracker): - """Test commands work with surrounding whitespace.""" - result = handle_command(" /quit ", mock_agent, mock_token_tracker) + @pytest.mark.parametrize( + "command", + ["/quit", "quit", " /quit "], + ids=["with-slash", "without-slash", "with-whitespace"], + ) + def test_command_formatting( + self, command: str, mock_agent: Mock, mock_token_tracker: Mock + ) -> None: + """Test commands work with/without leading slash and whitespace.""" + result = handle_command(command, mock_agent, mock_token_tracker) assert result == "exit" class TestExecuteBashCommand: """Tests for execute_bash_command function.""" - def test_execute_simple_command(self): + def test_execute_simple_command(self) -> None: """Test executing a simple bash command.""" with patch("subprocess.run") as mock_run: mock_run.return_value = subprocess.CompletedProcess( @@ -108,12 +91,12 @@ def test_execute_simple_command(self): assert result is True mock_run.assert_called_once() - def test_execute_empty_command(self): + def test_execute_empty_command(self) -> None: """Test that empty command is handled.""" result = execute_bash_command("!") assert result is True - def test_execute_command_with_stderr(self): + def test_execute_command_with_stderr(self) -> None: """Test command that produces stderr.""" with patch("subprocess.run") as mock_run: mock_run.return_value = subprocess.CompletedProcess( @@ -123,7 +106,7 @@ def test_execute_command_with_stderr(self): result = execute_bash_command("!ls nonexistent") assert result is True - def test_execute_command_timeout(self): + def test_execute_command_timeout(self) -> None: """Test command timeout handling.""" with patch("subprocess.run") as mock_run: mock_run.side_effect = subprocess.TimeoutExpired(cmd="sleep 100", timeout=30) @@ -131,7 +114,7 @@ def test_execute_command_timeout(self): result = execute_bash_command("!sleep 100") assert result is True - def test_execute_command_exception(self): + def test_execute_command_exception(self) -> None: """Test command execution exception handling.""" with patch("subprocess.run") as mock_run: mock_run.side_effect = Exception("Test error") @@ -139,7 +122,7 @@ def test_execute_command_exception(self): result = execute_bash_command("!invalid") assert result is True - def test_execute_command_strips_exclamation(self): + def test_execute_command_strips_exclamation(self) -> None: """Test that leading ! is stripped from command.""" with patch("subprocess.run") as mock_run: mock_run.return_value = subprocess.CompletedProcess( @@ -153,7 +136,7 @@ def test_execute_command_strips_exclamation(self): assert call_args[0][0] == "pwd" # Command without ! assert call_args[1]["shell"] is True - def test_execute_command_nonzero_exit_code(self): + def test_execute_command_nonzero_exit_code(self) -> None: """Test command with non-zero exit code.""" with patch("subprocess.run") as mock_run: mock_run.return_value = subprocess.CompletedProcess( @@ -163,7 +146,7 @@ def test_execute_command_nonzero_exit_code(self): result = execute_bash_command("!false") assert result is True - def test_execute_command_with_whitespace(self): + def test_execute_command_with_whitespace(self) -> None: """Test command with leading/trailing whitespace.""" with patch("subprocess.run") as mock_run: mock_run.return_value = subprocess.CompletedProcess( diff --git a/libs/deepagents-cli/tests/test_config.py b/libs/deepagents-cli/tests/test_config.py index 0f78daa0..7c1c7903 100644 --- a/libs/deepagents-cli/tests/test_config.py +++ b/libs/deepagents-cli/tests/test_config.py @@ -7,17 +7,13 @@ import pytest from deepagents_cli.config import ( - COLORS, - COMMANDS, - COMMON_BASH_COMMANDS, - MAX_ARG_LENGTH, SessionState, create_model, get_default_coding_instructions, ) -def test_session_state_initialization(): +def test_session_state_initialization() -> None: """Test SessionState initializes with correct defaults.""" state = SessionState() assert state.auto_approve is False @@ -26,7 +22,7 @@ def test_session_state_initialization(): assert state_with_approve.auto_approve is True -def test_session_state_toggle_auto_approve(): +def test_session_state_toggle_auto_approve() -> None: """Test SessionState toggle functionality.""" state = SessionState(auto_approve=False) @@ -41,7 +37,7 @@ def test_session_state_toggle_auto_approve(): assert state.auto_approve is False -def test_get_default_coding_instructions_returns_content(): +def test_get_default_coding_instructions_returns_content() -> None: """Test that get_default_coding_instructions reads and returns content.""" instructions = get_default_coding_instructions() @@ -50,7 +46,7 @@ def test_get_default_coding_instructions_returns_content(): assert len(instructions) > 0 -def test_get_default_coding_instructions_file_exists(): +def test_get_default_coding_instructions_file_exists() -> None: """Test that the default agent prompt file exists.""" from deepagents_cli import config @@ -59,31 +55,7 @@ def test_get_default_coding_instructions_file_exists(): assert prompt_path.is_file() -def test_constants_are_defined(): - """Test that important constants are defined correctly.""" - # Color constants - assert isinstance(COLORS, dict) - assert "primary" in COLORS - assert "agent" in COLORS - assert "user" in COLORS - - # Command constants - assert isinstance(COMMANDS, dict) - assert "clear" in COMMANDS - assert "help" in COMMANDS - assert "quit" in COMMANDS - - # Bash commands - assert isinstance(COMMON_BASH_COMMANDS, dict) - assert "ls" in COMMON_BASH_COMMANDS - assert "cd" in COMMON_BASH_COMMANDS - - # Max arg length - assert isinstance(MAX_ARG_LENGTH, int) - assert MAX_ARG_LENGTH > 0 - - -def test_create_model_no_api_keys(): +def test_create_model_no_api_keys() -> None: """Test that create_model exits when no API keys are configured.""" with patch.dict(os.environ, {}, clear=True): # Should exit with code 1 @@ -92,7 +64,7 @@ def test_create_model_no_api_keys(): assert exc_info.value.code == 1 -def test_create_model_with_openai_key(): +def test_create_model_with_openai_key() -> None: """Test that create_model returns OpenAI model when key is set.""" with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=True): model = create_model() @@ -100,7 +72,7 @@ def test_create_model_with_openai_key(): assert model.__class__.__name__ == "ChatOpenAI" -def test_create_model_with_anthropic_key(): +def test_create_model_with_anthropic_key() -> None: """Test that create_model returns Anthropic model when key is set.""" with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-key"}, clear=True): model = create_model() @@ -108,7 +80,7 @@ def test_create_model_with_anthropic_key(): assert model.__class__.__name__ == "ChatAnthropic" -def test_create_model_prefers_openai(): +def test_create_model_prefers_openai() -> None: """Test that create_model prefers OpenAI when both keys are set.""" with patch.dict( os.environ, @@ -120,7 +92,7 @@ def test_create_model_prefers_openai(): assert model.__class__.__name__ == "ChatOpenAI" -def test_create_model_custom_model_name_openai(): +def test_create_model_custom_model_name_openai() -> None: """Test that create_model respects custom OpenAI model name.""" with patch.dict( os.environ, {"OPENAI_API_KEY": "test-key", "OPENAI_MODEL": "gpt-4o"}, clear=True @@ -129,7 +101,7 @@ def test_create_model_custom_model_name_openai(): assert model.model_name == "gpt-4o" -def test_create_model_custom_model_name_anthropic(): +def test_create_model_custom_model_name_anthropic() -> None: """Test that create_model respects custom Anthropic model name.""" with patch.dict( os.environ, diff --git a/libs/deepagents-cli/tests/test_input_pure.py b/libs/deepagents-cli/tests/test_input_pure.py index b1883470..a7c3e2ae 100644 --- a/libs/deepagents-cli/tests/test_input_pure.py +++ b/libs/deepagents-cli/tests/test_input_pure.py @@ -6,13 +6,13 @@ class TestParseFileMentions: """Tests for parse_file_mentions function.""" - def test_no_mentions(self): + def test_no_mentions(self) -> None: """Test text with no @ mentions.""" text, files = parse_file_mentions("hello world") assert text == "hello world" assert files == [] - def test_single_file_mention(self, tmp_path): + def test_single_file_mention(self, tmp_path) -> None: """Test single @file mention.""" test_file = tmp_path / "test.txt" test_file.write_text("content") @@ -22,7 +22,7 @@ def test_single_file_mention(self, tmp_path): assert len(files) == 1 assert files[0] == test_file.resolve() - def test_multiple_file_mentions(self, tmp_path): + def test_multiple_file_mentions(self, tmp_path) -> None: """Test multiple @file mentions.""" file1 = tmp_path / "file1.txt" file2 = tmp_path / "file2.txt" @@ -34,13 +34,13 @@ def test_multiple_file_mentions(self, tmp_path): assert file1.resolve() in files assert file2.resolve() in files - def test_nonexistent_file(self, tmp_path): + def test_nonexistent_file(self, tmp_path) -> None: """Test @mention of nonexistent file.""" fake_file = tmp_path / "nonexistent.txt" text, files = parse_file_mentions(f"Read @{fake_file}") assert files == [] # Nonexistent files are filtered out - def test_file_with_spaces_escaped(self, tmp_path): + def test_file_with_spaces_escaped(self, tmp_path) -> None: """Test file with escaped spaces.""" file_with_space = tmp_path / "my file.txt" file_with_space.write_text("content") @@ -50,7 +50,7 @@ def test_file_with_spaces_escaped(self, tmp_path): assert len(files) == 1 assert files[0].name == "my file.txt" - def test_relative_path(self, tmp_path, monkeypatch): + def test_relative_path(self, tmp_path, monkeypatch) -> None: """Test relative path resolution.""" # Change to tmp_path monkeypatch.chdir(tmp_path) @@ -63,7 +63,7 @@ def test_relative_path(self, tmp_path, monkeypatch): assert len(files) == 1 assert files[0].name == "test.txt" - def test_absolute_path(self, tmp_path): + def test_absolute_path(self, tmp_path) -> None: """Test absolute path.""" test_file = tmp_path / "test.txt" test_file.write_text("content") @@ -72,7 +72,7 @@ def test_absolute_path(self, tmp_path): assert len(files) == 1 assert files[0] == test_file.resolve() - def test_expanduser_tilde(self): + def test_expanduser_tilde(self) -> None: """Test ~ expansion in paths uses user's home directory.""" # Create a test file in actual home directory (if accessible) # This is a lightweight test - just verify the function handles ~ @@ -83,7 +83,7 @@ def test_expanduser_tilde(self): # But text should be preserved assert text == "Read @~/nonexistent_test_file_12345.txt" - def test_directory_not_included(self, tmp_path): + def test_directory_not_included(self, tmp_path) -> None: """Test that directories are not included (only files).""" test_dir = tmp_path / "testdir" test_dir.mkdir() @@ -91,7 +91,7 @@ def test_directory_not_included(self, tmp_path): text, files = parse_file_mentions(f"Read @{test_dir}") assert files == [] # Directories should not be included - def test_multiple_at_symbols(self, tmp_path): + def test_multiple_at_symbols(self, tmp_path) -> None: """Test text with multiple @ symbols (not all file mentions).""" test_file = tmp_path / "file.txt" test_file.write_text("content") @@ -101,18 +101,18 @@ def test_multiple_at_symbols(self, tmp_path): assert len(files) == 1 assert files[0].name == "file.txt" - def test_at_symbol_at_end(self, tmp_path): + def test_at_symbol_at_end(self, tmp_path) -> None: """Test @ symbol at end of text.""" text, files = parse_file_mentions("Send to user@") assert files == [] - def test_empty_string(self): + def test_empty_string(self) -> None: """Test empty string input.""" text, files = parse_file_mentions("") assert text == "" assert files == [] - def test_text_preserved(self, tmp_path): + def test_text_preserved(self, tmp_path) -> None: """Test that original text is returned unchanged.""" test_file = tmp_path / "test.txt" test_file.write_text("content") @@ -121,7 +121,7 @@ def test_text_preserved(self, tmp_path): text, files = parse_file_mentions(original) assert text == original # Text should be unchanged - def test_special_characters_in_filename(self, tmp_path): + def test_special_characters_in_filename(self, tmp_path) -> None: """Test file with special characters.""" special_file = tmp_path / "file-name_123.txt" special_file.write_text("content") diff --git a/libs/deepagents-cli/tests/test_token_utils.py b/libs/deepagents-cli/tests/test_token_utils.py index 03e95264..bd904c44 100644 --- a/libs/deepagents-cli/tests/test_token_utils.py +++ b/libs/deepagents-cli/tests/test_token_utils.py @@ -5,7 +5,7 @@ from deepagents_cli.token_utils import calculate_baseline_tokens, get_memory_system_prompt -def test_get_memory_system_prompt_returns_string(): +def test_get_memory_system_prompt_returns_string() -> None: """Test that get_memory_system_prompt returns a formatted string.""" prompt = get_memory_system_prompt() @@ -15,7 +15,7 @@ def test_get_memory_system_prompt_returns_string(): assert "/memories/" in prompt -def test_get_memory_system_prompt_formatting(): +def test_get_memory_system_prompt_formatting() -> None: """Test that get_memory_system_prompt properly formats the template.""" prompt = get_memory_system_prompt() @@ -24,7 +24,7 @@ def test_get_memory_system_prompt_formatting(): assert "{memory_path}" not in prompt -def test_calculate_baseline_tokens_with_agent_md(tmp_path): +def test_calculate_baseline_tokens_with_agent_md(tmp_path) -> None: """Test calculate_baseline_tokens with an agent.md file.""" # Create a temporary agent directory with agent.md agent_dir = tmp_path / "agent" @@ -46,7 +46,7 @@ def test_calculate_baseline_tokens_with_agent_md(tmp_path): mock_model.get_num_tokens_from_messages.assert_called_once() -def test_calculate_baseline_tokens_without_agent_md(tmp_path): +def test_calculate_baseline_tokens_without_agent_md(tmp_path) -> None: """Test calculate_baseline_tokens without an agent.md file.""" # Create a temporary agent directory without agent.md agent_dir = tmp_path / "agent" @@ -65,7 +65,7 @@ def test_calculate_baseline_tokens_without_agent_md(tmp_path): mock_model.get_num_tokens_from_messages.assert_called_once() -def test_calculate_baseline_tokens_includes_system_components(tmp_path): +def test_calculate_baseline_tokens_includes_system_components(tmp_path) -> None: """Test that calculate_baseline_tokens includes all system prompt components.""" agent_dir = tmp_path / "agent" agent_dir.mkdir() @@ -95,7 +95,7 @@ def test_calculate_baseline_tokens_includes_system_components(tmp_path): assert "/memories/" in message_content -def test_calculate_baseline_tokens_handles_exception(tmp_path): +def test_calculate_baseline_tokens_handles_exception(tmp_path) -> None: """Test that calculate_baseline_tokens handles exceptions gracefully.""" agent_dir = tmp_path / "agent" agent_dir.mkdir() @@ -111,7 +111,7 @@ def test_calculate_baseline_tokens_handles_exception(tmp_path): assert tokens == 0 -def test_calculate_baseline_tokens_with_empty_agent_md(tmp_path): +def test_calculate_baseline_tokens_with_empty_agent_md(tmp_path) -> None: """Test calculate_baseline_tokens with an empty agent.md file.""" agent_dir = tmp_path / "agent" agent_dir.mkdir() @@ -129,7 +129,7 @@ def test_calculate_baseline_tokens_with_empty_agent_md(tmp_path): assert tokens == 75 -def test_calculate_baseline_tokens_creates_system_message(tmp_path): +def test_calculate_baseline_tokens_creates_system_message(tmp_path) -> None: """Test that calculate_baseline_tokens creates a proper SystemMessage.""" from langchain_core.messages import SystemMessage diff --git a/libs/deepagents-cli/tests/test_ui_pure.py b/libs/deepagents-cli/tests/test_ui_pure.py index 46216524..57c379cd 100644 --- a/libs/deepagents-cli/tests/test_ui_pure.py +++ b/libs/deepagents-cli/tests/test_ui_pure.py @@ -11,23 +11,23 @@ class TestTruncateValue: """Tests for truncate_value function.""" - def test_truncate_short_string(self): + def test_truncate_short_string(self) -> None: """Test that short strings are not truncated.""" result = truncate_value("hello", max_length=10) assert result == "hello" - def test_truncate_exact_length(self): + def test_truncate_exact_length(self) -> None: """Test string exactly at max length.""" result = truncate_value("1234567890", max_length=10) assert result == "1234567890" - def test_truncate_long_string(self): + def test_truncate_long_string(self) -> None: """Test that long strings are truncated with ellipsis.""" result = truncate_value("1234567890abc", max_length=10) assert result == "1234567890..." assert len(result) == 13 # 10 chars + "..." - def test_truncate_default_max_length(self): + def test_truncate_default_max_length(self) -> None: """Test truncate uses default MAX_ARG_LENGTH.""" from deepagents_cli.config import MAX_ARG_LENGTH @@ -36,12 +36,12 @@ def test_truncate_default_max_length(self): assert result.endswith("...") assert len(result) == MAX_ARG_LENGTH + 3 - def test_truncate_empty_string(self): + def test_truncate_empty_string(self) -> None: """Test empty string returns empty.""" result = truncate_value("") assert result == "" - def test_truncate_with_special_characters(self): + def test_truncate_with_special_characters(self) -> None: """Test truncation preserves special characters.""" result = truncate_value("hello🌟world", max_length=8) assert result == "hello🌟wo..." @@ -50,29 +50,29 @@ def test_truncate_with_special_characters(self): class TestFormatToolDisplay: """Tests for format_tool_display function.""" - def test_read_file_with_file_path(self): + def test_read_file_with_file_path(self) -> None: """Test read_file shows abbreviated path.""" result = format_tool_display("read_file", {"file_path": "/long/path/to/file.py"}) assert "read_file" in result assert "file.py" in result - def test_write_file_with_path(self): + def test_write_file_with_path(self) -> None: """Test write_file with 'path' argument.""" result = format_tool_display("write_file", {"path": "config.yaml"}) assert "write_file" in result assert "config.yaml" in result - def test_edit_file(self): + def test_edit_file(self) -> None: """Test edit_file formatting.""" result = format_tool_display("edit_file", {"file_path": "main.py"}) assert result == "edit_file(main.py)" - def test_web_search(self): + def test_web_search(self) -> None: """Test web_search shows query.""" result = format_tool_display("web_search", {"query": "how to code in python"}) assert result == 'web_search("how to code in python")' - def test_web_search_long_query(self): + def test_web_search_long_query(self) -> None: """Test web_search truncates long queries.""" long_query = "x" * 150 result = format_tool_display("web_search", {"query": long_query}) @@ -80,39 +80,39 @@ def test_web_search_long_query(self): # Should be truncated to 100 chars + "..." assert len(result) <= len('web_search("")') + 100 + 3 + 10 - def test_grep_with_pattern(self): + def test_grep_with_pattern(self) -> None: """Test grep shows search pattern.""" result = format_tool_display("grep", {"pattern": "def.*main"}) assert result == 'grep("def.*main")' - def test_shell_command(self): + def test_shell_command(self) -> None: """Test shell shows command.""" result = format_tool_display("shell", {"command": "git status"}) assert result == 'shell("git status")' - def test_shell_long_command(self): + def test_shell_long_command(self) -> None: """Test shell truncates long commands.""" long_cmd = "echo " + "x" * 200 result = format_tool_display("shell", {"command": long_cmd}) assert "..." in result - def test_ls_with_path(self): + def test_ls_with_path(self) -> None: """Test ls with directory path.""" result = format_tool_display("ls", {"path": "/home/user"}) assert "ls(" in result assert "home" in result or "user" in result - def test_ls_without_path(self): + def test_ls_without_path(self) -> None: """Test ls without path shows empty parens.""" result = format_tool_display("ls", {}) assert result == "ls()" - def test_glob_with_pattern(self): + def test_glob_with_pattern(self) -> None: """Test glob shows pattern.""" result = format_tool_display("glob", {"pattern": "**/*.py"}) assert result == 'glob("**/*.py")' - def test_http_request_get(self): + def test_http_request_get(self) -> None: """Test HTTP request formatting.""" result = format_tool_display( "http_request", {"method": "GET", "url": "https://example.com/api"} @@ -121,51 +121,51 @@ def test_http_request_get(self): assert "GET" in result assert "example.com" in result - def test_http_request_post(self): + def test_http_request_post(self) -> None: """Test HTTP POST request.""" result = format_tool_display("http_request", {"method": "post", "url": "https://api.test"}) assert "POST" in result # Should be uppercase - def test_http_request_long_url(self): + def test_http_request_long_url(self) -> None: """Test HTTP request with long URL.""" long_url = "https://example.com/" + "path/" * 50 result = format_tool_display("http_request", {"method": "GET", "url": long_url}) assert "..." in result - def test_task_with_description(self): + def test_task_with_description(self) -> None: """Test task formatting.""" result = format_tool_display("task", {"description": "Install dependencies"}) assert result == 'task("Install dependencies")' - def test_write_todos(self): + def test_write_todos(self) -> None: """Test write_todos shows count.""" todos = [{"content": "Task 1"}, {"content": "Task 2"}, {"content": "Task 3"}] result = format_tool_display("write_todos", {"todos": todos}) assert result == "write_todos(3 items)" - def test_write_todos_single_item(self): + def test_write_todos_single_item(self) -> None: """Test write_todos with single item.""" result = format_tool_display("write_todos", {"todos": [{"content": "Task"}]}) assert result == "write_todos(1 items)" - def test_write_todos_empty_list(self): + def test_write_todos_empty_list(self) -> None: """Test write_todos with empty list.""" result = format_tool_display("write_todos", {"todos": []}) assert result == "write_todos(0 items)" - def test_unknown_tool_fallback(self): + def test_unknown_tool_fallback(self) -> None: """Test unknown tool uses fallback formatting.""" result = format_tool_display("unknown_tool", {"arg1": "value1", "arg2": "value2"}) assert "unknown_tool" in result assert "arg1" in result assert "value1" in result - def test_tool_with_no_args(self): + def test_tool_with_no_args(self) -> None: """Test tool with no arguments.""" result = format_tool_display("some_tool", {}) assert result == "some_tool()" - def test_read_file_simple_filename(self): + def test_read_file_simple_filename(self) -> None: """Test read_file with simple filename (no path).""" result = format_tool_display("read_file", {"file_path": "README.md"}) assert result == "read_file(README.md)" @@ -174,22 +174,22 @@ def test_read_file_simple_filename(self): class TestFormatToolMessageContent: """Tests for format_tool_message_content function.""" - def test_none_content(self): + def test_none_content(self) -> None: """Test None returns empty string.""" result = format_tool_message_content(None) assert result == "" - def test_string_content(self): + def test_string_content(self) -> None: """Test string content is returned as-is.""" result = format_tool_message_content("hello world") assert result == "hello world" - def test_list_of_strings(self): + def test_list_of_strings(self) -> None: """Test list of strings joined with newlines.""" result = format_tool_message_content(["line1", "line2", "line3"]) assert result == "line1\nline2\nline3" - def test_list_with_dicts(self): + def test_list_with_dicts(self) -> None: """Test list containing dicts converted to JSON.""" content = [{"key": "value"}, {"foo": "bar"}] result = format_tool_message_content(content) @@ -197,7 +197,7 @@ def test_list_with_dicts(self): assert '{"foo": "bar"}' in result assert "\n" in result - def test_list_mixed_types(self): + def test_list_mixed_types(self) -> None: """Test list with mixed types.""" content = ["text", {"dict": "value"}, 123] result = format_tool_message_content(content) @@ -205,23 +205,23 @@ def test_list_mixed_types(self): assert '"dict"' in result assert "123" in result - def test_number_content(self): + def test_number_content(self) -> None: """Test number is converted to string.""" result = format_tool_message_content(42) assert result == "42" - def test_dict_content(self): + def test_dict_content(self) -> None: """Test dict is converted to string.""" result = format_tool_message_content({"key": "value"}) assert "key" in result assert "value" in result - def test_empty_list(self): + def test_empty_list(self) -> None: """Test empty list returns empty string.""" result = format_tool_message_content([]) assert result == "" - def test_list_with_none_values(self): + def test_list_with_none_values(self) -> None: """Test list containing None values.""" result = format_tool_message_content([None, "text", None]) # Should handle None gracefully @@ -231,37 +231,37 @@ def test_list_with_none_values(self): class TestFormatLineSpan: """Tests for _format_line_span function.""" - def test_both_none(self): + def test_both_none(self) -> None: """Test when both start and end are None.""" result = _format_line_span(None, None) assert result == "" - def test_only_start(self): + def test_only_start(self) -> None: """Test when only start is provided.""" result = _format_line_span(10, None) assert result == "(starting at line 10)" - def test_only_end(self): + def test_only_end(self) -> None: """Test when only end is provided.""" result = _format_line_span(None, 20) assert result == "(through line 20)" - def test_same_line(self): + def test_same_line(self) -> None: """Test when start equals end.""" result = _format_line_span(5, 5) assert result == "(line 5)" - def test_line_range(self): + def test_line_range(self) -> None: """Test when start and end are different.""" result = _format_line_span(1, 10) assert result == "(lines 1-10)" - def test_large_range(self): + def test_large_range(self) -> None: """Test with large line numbers.""" result = _format_line_span(1000, 2000) assert result == "(lines 1000-2000)" - def test_single_digit_lines(self): + def test_single_digit_lines(self) -> None: """Test with single digit line numbers.""" result = _format_line_span(1, 9) assert result == "(lines 1-9)" From 75d7ab62b156dd1b70bcbe3d268820bb28bfc5d5 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Fri, 31 Oct 2025 16:52:27 -0400 Subject: [PATCH 3/6] x --- libs/deepagents-cli/tests/test_file_ops.py | 9 +++--- libs/deepagents-cli/tests/test_input_pure.py | 29 +++++++++++-------- libs/deepagents-cli/tests/test_token_utils.py | 13 +++++---- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/libs/deepagents-cli/tests/test_file_ops.py b/libs/deepagents-cli/tests/test_file_ops.py index d167dead..58fa3203 100644 --- a/libs/deepagents-cli/tests/test_file_ops.py +++ b/libs/deepagents-cli/tests/test_file_ops.py @@ -1,11 +1,12 @@ import textwrap +from pathlib import Path from langchain_core.messages import ToolMessage from deepagents_cli.file_ops import FileOpTracker, build_approval_preview -def test_tracker_records_read_lines(tmp_path): +def test_tracker_records_read_lines(tmp_path: Path) -> None: tracker = FileOpTracker(assistant_id=None) path = tmp_path / "example.py" @@ -28,7 +29,7 @@ def test_tracker_records_read_lines(tmp_path): assert record.metrics.end_line == 2 -def test_tracker_records_write_diff(tmp_path): +def test_tracker_records_write_diff(tmp_path: Path) -> None: tracker = FileOpTracker(assistant_id=None) file_path = tmp_path / "created.txt" @@ -54,7 +55,7 @@ def test_tracker_records_write_diff(tmp_path): assert "+hello world" in record.diff -def test_tracker_records_edit_diff(tmp_path): +def test_tracker_records_edit_diff(tmp_path: Path) -> None: tracker = FileOpTracker(assistant_id=None) file_path = tmp_path / "functions.py" file_path.write_text( @@ -99,7 +100,7 @@ def wave(): assert '+ return "hi"' in record.diff -def test_build_approval_preview_generates_diff(tmp_path): +def test_build_approval_preview_generates_diff(tmp_path: Path) -> None: target = tmp_path / "notes.txt" target.write_text("alpha\nbeta\n") diff --git a/libs/deepagents-cli/tests/test_input_pure.py b/libs/deepagents-cli/tests/test_input_pure.py index a7c3e2ae..a97f1d40 100644 --- a/libs/deepagents-cli/tests/test_input_pure.py +++ b/libs/deepagents-cli/tests/test_input_pure.py @@ -1,5 +1,9 @@ """Tests for input parsing functions (no mocks needed).""" +from pathlib import Path + +import pytest + from deepagents_cli.input import parse_file_mentions @@ -12,7 +16,7 @@ def test_no_mentions(self) -> None: assert text == "hello world" assert files == [] - def test_single_file_mention(self, tmp_path) -> None: + def test_single_file_mention(self, tmp_path: Path) -> None: """Test single @file mention.""" test_file = tmp_path / "test.txt" test_file.write_text("content") @@ -22,7 +26,7 @@ def test_single_file_mention(self, tmp_path) -> None: assert len(files) == 1 assert files[0] == test_file.resolve() - def test_multiple_file_mentions(self, tmp_path) -> None: + def test_multiple_file_mentions(self, tmp_path: Path) -> None: """Test multiple @file mentions.""" file1 = tmp_path / "file1.txt" file2 = tmp_path / "file2.txt" @@ -34,23 +38,24 @@ def test_multiple_file_mentions(self, tmp_path) -> None: assert file1.resolve() in files assert file2.resolve() in files - def test_nonexistent_file(self, tmp_path) -> None: + def test_nonexistent_file(self, tmp_path: Path) -> None: """Test @mention of nonexistent file.""" fake_file = tmp_path / "nonexistent.txt" text, files = parse_file_mentions(f"Read @{fake_file}") assert files == [] # Nonexistent files are filtered out - def test_file_with_spaces_escaped(self, tmp_path) -> None: + def test_file_with_spaces_escaped(self, tmp_path: Path) -> None: """Test file with escaped spaces.""" file_with_space = tmp_path / "my file.txt" file_with_space.write_text("content") # Use escaped space - text, files = parse_file_mentions(f"Read @{str(file_with_space).replace(' ', r'\ ')}") + escaped_path = str(file_with_space).replace(" ", r"\ ") + text, files = parse_file_mentions(f"Read @{escaped_path}") assert len(files) == 1 assert files[0].name == "my file.txt" - def test_relative_path(self, tmp_path, monkeypatch) -> None: + def test_relative_path(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: """Test relative path resolution.""" # Change to tmp_path monkeypatch.chdir(tmp_path) @@ -63,7 +68,7 @@ def test_relative_path(self, tmp_path, monkeypatch) -> None: assert len(files) == 1 assert files[0].name == "test.txt" - def test_absolute_path(self, tmp_path) -> None: + def test_absolute_path(self, tmp_path: Path) -> None: """Test absolute path.""" test_file = tmp_path / "test.txt" test_file.write_text("content") @@ -83,7 +88,7 @@ def test_expanduser_tilde(self) -> None: # But text should be preserved assert text == "Read @~/nonexistent_test_file_12345.txt" - def test_directory_not_included(self, tmp_path) -> None: + def test_directory_not_included(self, tmp_path: Path) -> None: """Test that directories are not included (only files).""" test_dir = tmp_path / "testdir" test_dir.mkdir() @@ -91,7 +96,7 @@ def test_directory_not_included(self, tmp_path) -> None: text, files = parse_file_mentions(f"Read @{test_dir}") assert files == [] # Directories should not be included - def test_multiple_at_symbols(self, tmp_path) -> None: + def test_multiple_at_symbols(self, tmp_path: Path) -> None: """Test text with multiple @ symbols (not all file mentions).""" test_file = tmp_path / "file.txt" test_file.write_text("content") @@ -101,7 +106,7 @@ def test_multiple_at_symbols(self, tmp_path) -> None: assert len(files) == 1 assert files[0].name == "file.txt" - def test_at_symbol_at_end(self, tmp_path) -> None: + def test_at_symbol_at_end(self) -> None: """Test @ symbol at end of text.""" text, files = parse_file_mentions("Send to user@") assert files == [] @@ -112,7 +117,7 @@ def test_empty_string(self) -> None: assert text == "" assert files == [] - def test_text_preserved(self, tmp_path) -> None: + def test_text_preserved(self, tmp_path: Path) -> None: """Test that original text is returned unchanged.""" test_file = tmp_path / "test.txt" test_file.write_text("content") @@ -121,7 +126,7 @@ def test_text_preserved(self, tmp_path) -> None: text, files = parse_file_mentions(original) assert text == original # Text should be unchanged - def test_special_characters_in_filename(self, tmp_path) -> None: + def test_special_characters_in_filename(self, tmp_path: Path) -> None: """Test file with special characters.""" special_file = tmp_path / "file-name_123.txt" special_file.write_text("content") diff --git a/libs/deepagents-cli/tests/test_token_utils.py b/libs/deepagents-cli/tests/test_token_utils.py index bd904c44..8bc073b2 100644 --- a/libs/deepagents-cli/tests/test_token_utils.py +++ b/libs/deepagents-cli/tests/test_token_utils.py @@ -1,5 +1,6 @@ """Tests for token counting utilities.""" +from pathlib import Path from unittest.mock import Mock from deepagents_cli.token_utils import calculate_baseline_tokens, get_memory_system_prompt @@ -24,7 +25,7 @@ def test_get_memory_system_prompt_formatting() -> None: assert "{memory_path}" not in prompt -def test_calculate_baseline_tokens_with_agent_md(tmp_path) -> None: +def test_calculate_baseline_tokens_with_agent_md(tmp_path: Path) -> None: """Test calculate_baseline_tokens with an agent.md file.""" # Create a temporary agent directory with agent.md agent_dir = tmp_path / "agent" @@ -46,7 +47,7 @@ def test_calculate_baseline_tokens_with_agent_md(tmp_path) -> None: mock_model.get_num_tokens_from_messages.assert_called_once() -def test_calculate_baseline_tokens_without_agent_md(tmp_path) -> None: +def test_calculate_baseline_tokens_without_agent_md(tmp_path: Path) -> None: """Test calculate_baseline_tokens without an agent.md file.""" # Create a temporary agent directory without agent.md agent_dir = tmp_path / "agent" @@ -65,7 +66,7 @@ def test_calculate_baseline_tokens_without_agent_md(tmp_path) -> None: mock_model.get_num_tokens_from_messages.assert_called_once() -def test_calculate_baseline_tokens_includes_system_components(tmp_path) -> None: +def test_calculate_baseline_tokens_includes_system_components(tmp_path: Path) -> None: """Test that calculate_baseline_tokens includes all system prompt components.""" agent_dir = tmp_path / "agent" agent_dir.mkdir() @@ -95,7 +96,7 @@ def test_calculate_baseline_tokens_includes_system_components(tmp_path) -> None: assert "/memories/" in message_content -def test_calculate_baseline_tokens_handles_exception(tmp_path) -> None: +def test_calculate_baseline_tokens_handles_exception(tmp_path: Path) -> None: """Test that calculate_baseline_tokens handles exceptions gracefully.""" agent_dir = tmp_path / "agent" agent_dir.mkdir() @@ -111,7 +112,7 @@ def test_calculate_baseline_tokens_handles_exception(tmp_path) -> None: assert tokens == 0 -def test_calculate_baseline_tokens_with_empty_agent_md(tmp_path) -> None: +def test_calculate_baseline_tokens_with_empty_agent_md(tmp_path: Path) -> None: """Test calculate_baseline_tokens with an empty agent.md file.""" agent_dir = tmp_path / "agent" agent_dir.mkdir() @@ -129,7 +130,7 @@ def test_calculate_baseline_tokens_with_empty_agent_md(tmp_path) -> None: assert tokens == 75 -def test_calculate_baseline_tokens_creates_system_message(tmp_path) -> None: +def test_calculate_baseline_tokens_creates_system_message(tmp_path: Path) -> None: """Test that calculate_baseline_tokens creates a proper SystemMessage.""" from langchain_core.messages import SystemMessage From 739a934a841c836227974d73ea5e15d520bc8851 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Fri, 31 Oct 2025 16:52:29 -0400 Subject: [PATCH 4/6] x --- libs/deepagents-cli/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/deepagents-cli/pyproject.toml b/libs/deepagents-cli/pyproject.toml index 19a7f495..21227a1a 100644 --- a/libs/deepagents-cli/pyproject.toml +++ b/libs/deepagents-cli/pyproject.toml @@ -100,6 +100,7 @@ ignore-var-parameters = true "S311", # Allow pseudo-random generators in tests "ANN201", # Missing return type annotation "INP001", # Implicit namespace package + "PLR2004", # Allow magic values in tests ] [tool.mypy] From afc1527b331876511d52ead7fa1de60a25ad3098 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Fri, 31 Oct 2025 16:55:24 -0400 Subject: [PATCH 5/6] x --- libs/deepagents-cli/.gitignore | 38 ++++++++++++++++++++++++++++++++++ libs/deepagents-cli/Makefile | 6 +++--- 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 libs/deepagents-cli/.gitignore diff --git a/libs/deepagents-cli/.gitignore b/libs/deepagents-cli/.gitignore new file mode 100644 index 00000000..50d30d15 --- /dev/null +++ b/libs/deepagents-cli/.gitignore @@ -0,0 +1,38 @@ +# Coverage reports +htmlcov/ +.coverage +.coverage.* + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ diff --git a/libs/deepagents-cli/Makefile b/libs/deepagents-cli/Makefile index 93ad4394..926fd28e 100644 --- a/libs/deepagents-cli/Makefile +++ b/libs/deepagents-cli/Makefile @@ -11,7 +11,7 @@ all: help TEST_FILE ?= tests/ test: - uv run pytest --disable-socket --allow-unix-socket $(TEST_FILE) --timeout 10 + uv run pytest --disable-socket --allow-unix-socket $(TEST_FILE) --timeout 10 --cov=deepagents_cli --cov-report=term-missing --cov-report=html test_watch: uv run ptw . -- $(TEST_FILE) @@ -46,8 +46,8 @@ help: @echo 'format - run code formatters' @echo 'lint - run linters' @echo '-- TESTS --' - @echo 'test - run unit tests' - @echo 'test TEST_FILE= - run all tests in file' + @echo 'test - run unit tests with coverage' + @echo 'test TEST_FILE= - run all tests in file with coverage' @echo '-- DOCUMENTATION tasks are from the top-level Makefile --' From 07a6430e194e539777eb606c433c53151cbb383f Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Fri, 31 Oct 2025 16:57:39 -0400 Subject: [PATCH 6/6] x --- libs/deepagents-cli/.gitignore | 38 ---------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 libs/deepagents-cli/.gitignore diff --git a/libs/deepagents-cli/.gitignore b/libs/deepagents-cli/.gitignore deleted file mode 100644 index 50d30d15..00000000 --- a/libs/deepagents-cli/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -# Coverage reports -htmlcov/ -.coverage -.coverage.* - -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg - -# Virtual environments -venv/ -ENV/ -env/ - -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~