tunacode-cli 0.0.38__tar.gz → 0.0.40__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tunacode-cli might be problematic. Click here for more details.
- {tunacode_cli-0.0.38/src/tunacode_cli.egg-info → tunacode_cli-0.0.40}/PKG-INFO +2 -1
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/pyproject.toml +2 -1
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/constants.py +11 -1
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/prompts/system.md +49 -12
- tunacode_cli-0.0.40/src/tunacode/utils/text_utils.py +206 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40/src/tunacode_cli.egg-info}/PKG-INFO +2 -1
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/SOURCES.txt +3 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/requires.txt +1 -0
- tunacode_cli-0.0.40/tests/characterization/utils/test_expand_file_refs.py +179 -0
- tunacode_cli-0.0.40/tests/test_agent_output_formatting.py +107 -0
- tunacode_cli-0.0.40/tests/test_prompt_changes_validation.py +35 -0
- tunacode_cli-0.0.38/src/tunacode/utils/text_utils.py +0 -100
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/CLAUDE.md +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/LICENSE +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/MANIFEST.in +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/README.md +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/TUNACODE.md +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/setup.cfg +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/setup.py +0 -0
- {tunacode_cli-0.0.38/src/tunacode/utils → tunacode_cli-0.0.40/src/tunacode}/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/base.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/conversation.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/debug.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/development.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/model.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/system.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/registry.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/main.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/repl.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/textual_app.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/cli/textual_bridge.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/configuration/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/configuration/defaults.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/configuration/models.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/configuration/settings.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/context.py +0 -0
- {tunacode_cli-0.0.38/src/tunacode/core/llm → tunacode_cli-0.0.40/src/tunacode/core}/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/agents/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/agents/main.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/agents/utils.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/background/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/background/manager.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/code_index.py +0 -0
- {tunacode_cli-0.0.38/src/tunacode/core → tunacode_cli-0.0.40/src/tunacode/core/llm}/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/setup/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/setup/agent_setup.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/setup/base.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/setup/config_setup.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/setup/coordinator.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/setup/environment_setup.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/setup/git_safety_setup.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/state.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/core/tool_handler.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/exceptions.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/py.typed +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/services/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/services/mcp.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/setup.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/base.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/bash.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/glob.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/grep.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/list_dir.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/read_file.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/read_file_async_poc.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/run_command.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/update_file.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/tools/write_file.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/types.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/completers.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/console.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/constants.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/decorators.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/input.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/keybindings.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/lexers.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/output.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/panels.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/prompt_manager.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/tool_ui.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/utils.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/ui/validators.py +0 -0
- {tunacode_cli-0.0.38/src/tunacode → tunacode_cli-0.0.40/src/tunacode/utils}/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/utils/bm25.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/utils/diff_utils.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/utils/file_utils.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/utils/import_cache.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/utils/ripgrep.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/utils/security.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/utils/system.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/utils/token_counter.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode/utils/user_configuration.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/top_level.txt +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/agent/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/agent/conftest.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/agent/test_agent_creation.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/agent/test_json_tool_parsing.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/agent/test_process_node.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/agent/test_process_request.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/agent/test_tool_message_patching.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/background/test_background_edge_cases.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/background/test_cleanup.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/background/test_task_cancellation.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/background/test_task_creation.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/background/test_task_execution.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_cache_management.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_file_scanning.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_index_building.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_search_operations.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_symbol_extraction.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/commands/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/commands/test_init_command.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/conftest.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/context/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/context/test_context_acceptance.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/context/test_context_integration.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/context/test_context_loading.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/context/test_tunacode_logging.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/repl/test_command_parsing.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/repl/test_input_handling.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/repl/test_keyboard_interrupts.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/repl/test_multiline_input.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/repl/test_repl_initialization.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/repl/test_session_flow.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/services/test_error_recovery.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/services/test_llm_routing.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/services/test_mcp_integration.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/services/test_service_lifecycle.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/state/test_agent_tracking.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/state/test_message_history.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/state/test_permissions.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/state/test_session_management.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/state/test_state_initialization.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/state/test_user_config.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/test_characterization_commands.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/ui/test_async_ui.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/ui/test_console_output.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/ui/test_diff_display.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/ui/test_prompt_rendering.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/ui/test_tool_confirmations.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/utils/test_file_operations.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/utils/test_git_commands.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/utils/test_token_counting.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/characterization/utils/test_utils_edge_cases.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/conftest.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/crud/test_core_file_operations.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/fixtures/__init__.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/fixtures/file_operations.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/integration/test_error_recovery_flow.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/integration/test_full_session_flow.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/integration/test_mcp_tool_flow.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/integration/test_multi_tool_operations.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/integration/test_performance_scenarios.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_actual_parallelism.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_agent_initialization.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_background_manager.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_agent_main.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_bash.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_commands_system.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_glob.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_grep.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_grep_performance.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_iteration_limits.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_list_dir.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_read_file.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_repl_utils.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_run_command.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_setup_system.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_tool_ui_behavior.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_update_file.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_utilities.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_characterization_write_file.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_cli_command_flow.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_cli_file_operations_integration.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_config_directory_creation.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_config_setup_async.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_enhanced_visual_feedback.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_fallback_responses.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_fast_glob_search.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_file_operations_edge_cases.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_file_operations_stress.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_file_reference_context_tracking.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_file_reference_expansion.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_grep_fast_glob.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_grep_legacy_compat.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_grep_timeout.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_json_tool_parsing.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_list_dir.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_parallel_execution_demo.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_parallel_execution_freeze_fix.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_parallel_execution_integration.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_parallel_read_only_tools.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_parallel_tool_execution.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_read_only_confirmation.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_security.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_streaming_panel_tool_confirmation.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_streaming_spinner_conflict.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_tool_categorization.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_tool_combinations.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_tool_handler_ui_messages.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_update_command.py +0 -0
- {tunacode_cli-0.0.38 → tunacode_cli-0.0.40}/tests/test_visual_parallel_feedback.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tunacode-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.40
|
|
4
4
|
Summary: Your agentic CLI developer.
|
|
5
5
|
Author-email: larock22 <noreply@github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -31,6 +31,7 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
31
31
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
32
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
33
33
|
Requires-Dist: textual-dev; extra == "dev"
|
|
34
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
34
35
|
Dynamic: license-file
|
|
35
36
|
|
|
36
37
|
# TunaCode
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tunacode-cli"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.40"
|
|
8
8
|
description = "Your agentic CLI developer."
|
|
9
9
|
keywords = ["cli", "agent", "development", "automation"]
|
|
10
10
|
readme = "README.md"
|
|
@@ -43,6 +43,7 @@ dev = [
|
|
|
43
43
|
"pytest-cov",
|
|
44
44
|
"pytest-asyncio",
|
|
45
45
|
"textual-dev",
|
|
46
|
+
"pre-commit",
|
|
46
47
|
]
|
|
47
48
|
|
|
48
49
|
[project.urls]
|
|
@@ -7,7 +7,7 @@ Centralizes all magic strings, UI text, error messages, and application constant
|
|
|
7
7
|
|
|
8
8
|
# Application info
|
|
9
9
|
APP_NAME = "TunaCode"
|
|
10
|
-
APP_VERSION = "0.0.
|
|
10
|
+
APP_VERSION = "0.0.40"
|
|
11
11
|
|
|
12
12
|
# File patterns
|
|
13
13
|
GUIDE_FILE_PATTERN = "{name}.md"
|
|
@@ -18,6 +18,9 @@ CONFIG_FILE_NAME = "tunacode.json"
|
|
|
18
18
|
# Default limits
|
|
19
19
|
MAX_FILE_SIZE = 100 * 1024 # 100KB
|
|
20
20
|
MAX_COMMAND_OUTPUT = 5000 # 5000 chars
|
|
21
|
+
MAX_FILES_IN_DIR = 50
|
|
22
|
+
MAX_TOTAL_DIR_SIZE = 2 * 1024 * 1024 # 2 MB
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
# Command output processing
|
|
23
26
|
COMMAND_OUTPUT_THRESHOLD = 3500 # Length threshold for truncation
|
|
@@ -115,6 +118,13 @@ ERROR_COMMAND_NOT_FOUND = "Error: Command not found or failed to execute:"
|
|
|
115
118
|
ERROR_COMMAND_EXECUTION = (
|
|
116
119
|
"Error: Command not found or failed to execute: {command}. Details: {error}"
|
|
117
120
|
)
|
|
121
|
+
# Directory expansion errors
|
|
122
|
+
ERROR_DIR_TOO_LARGE = (
|
|
123
|
+
"Error: Directory '{path}' expansion aborted. Total size exceeds {limit_mb:.1f} MB limit."
|
|
124
|
+
)
|
|
125
|
+
ERROR_DIR_TOO_MANY_FILES = (
|
|
126
|
+
"Error: Directory '{path}' expansion aborted. Exceeds limit of {limit} files."
|
|
127
|
+
)
|
|
118
128
|
|
|
119
129
|
# Command output messages
|
|
120
130
|
CMD_OUTPUT_NO_OUTPUT = "No output."
|
|
@@ -256,13 +256,48 @@ If deeper exploration needed:
|
|
|
256
256
|
|
|
257
257
|
\###Meta Behavior###
|
|
258
258
|
|
|
259
|
-
Use the **ReAct** (Reasoning + Action) framework:
|
|
259
|
+
Use the **ReAct** (Reasoning + Action) framework internally:
|
|
260
260
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
-
|
|
265
|
-
-
|
|
261
|
+
**IMPORTANT**: Thoughts are for internal reasoning only. NEVER include JSON-formatted thoughts in your responses to users.
|
|
262
|
+
|
|
263
|
+
Internal process (not shown to user):
|
|
264
|
+
- Think: "I need to inspect the file before modifying."
|
|
265
|
+
- Act: run tool
|
|
266
|
+
- Think: "I see the old import. Now I'll patch it."
|
|
267
|
+
- Act: update file
|
|
268
|
+
- Think: "Patch complete. Ready for next instruction."
|
|
269
|
+
|
|
270
|
+
**Your responses to users should be clean, formatted text without JSON artifacts.**
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
\###Output Formatting Rules###
|
|
275
|
+
|
|
276
|
+
**CRITICAL**: Your responses to users must be clean, readable text:
|
|
277
|
+
|
|
278
|
+
1. **NO JSON in responses** - Never output {"thought": ...}, {"suggestions": ...}, or any JSON to users
|
|
279
|
+
2. **Use markdown formatting** - Use headers, lists, code blocks for readability
|
|
280
|
+
3. **Be direct and clear** - Provide actionable feedback and concrete suggestions
|
|
281
|
+
4. **Format suggestions as numbered or bulleted lists** - Not as JSON arrays
|
|
282
|
+
|
|
283
|
+
**Example of GOOD response formatting:**
|
|
284
|
+
```
|
|
285
|
+
Code Review Results:
|
|
286
|
+
|
|
287
|
+
The JavaScript code has good structure. Here are suggestions for improvement:
|
|
288
|
+
|
|
289
|
+
1. **Add comments** - Document major functions for better maintainability
|
|
290
|
+
2. **Consistent error handling** - Use try-catch blocks consistently
|
|
291
|
+
3. **Form validation** - Validate before submitting to ensure fields are filled
|
|
292
|
+
|
|
293
|
+
These changes will improve maintainability and user experience.
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Example of BAD response formatting (DO NOT DO THIS):**
|
|
297
|
+
```
|
|
298
|
+
{"thought": "Reviewing the code..."}
|
|
299
|
+
{"suggestions": ["Add comments", "Error handling", "Validation"]}
|
|
300
|
+
```
|
|
266
301
|
|
|
267
302
|
---
|
|
268
303
|
|
|
@@ -281,12 +316,13 @@ When users provide @ file references, they want information, not file creation.
|
|
|
281
316
|
```plaintext
|
|
282
317
|
User: What's the current app version?
|
|
283
318
|
|
|
284
|
-
|
|
319
|
+
[Internal thinking - not shown to user]
|
|
285
320
|
ACT: grep("APP_VERSION", ".")
|
|
286
|
-
|
|
321
|
+
[Found APP_VERSION in constants.py at line 12]
|
|
287
322
|
ACT: read_file("constants.py")
|
|
288
|
-
|
|
289
|
-
|
|
323
|
+
[APP_VERSION is set to '2.4.1']
|
|
324
|
+
|
|
325
|
+
RESPONSE TO USER: Current version is 2.4.1 (from constants.py)
|
|
290
326
|
```
|
|
291
327
|
|
|
292
328
|
````plaintext
|
|
@@ -300,8 +336,9 @@ def main():
|
|
|
300
336
|
|
|
301
337
|
=== END FILE REFERENCE: src/main.py ===
|
|
302
338
|
|
|
303
|
-
|
|
304
|
-
|
|
339
|
+
[Internal: User is asking about the referenced file, not asking me to create it]
|
|
340
|
+
|
|
341
|
+
RESPONSE TO USER: The main.py file contains a simple main function that prints 'Hello World'.
|
|
305
342
|
|
|
306
343
|
```
|
|
307
344
|
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: sidekick.utils.text_utils
|
|
3
|
+
|
|
4
|
+
Provides text processing utilities.
|
|
5
|
+
Includes file extension to language mapping and key formatting functions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import List, Set, Tuple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def key_to_title(key: str, uppercase_words: Set[str] = None) -> str:
|
|
13
|
+
"""Convert key to title, replacing underscores with spaces and capitalizing words."""
|
|
14
|
+
if uppercase_words is None:
|
|
15
|
+
uppercase_words = {"api", "id", "url"}
|
|
16
|
+
|
|
17
|
+
words = key.split("_")
|
|
18
|
+
result_words = []
|
|
19
|
+
for word in words:
|
|
20
|
+
lower_word = word.lower()
|
|
21
|
+
if lower_word in uppercase_words:
|
|
22
|
+
result_words.append(lower_word.upper())
|
|
23
|
+
elif word:
|
|
24
|
+
result_words.append(word[0].upper() + word[1:].lower())
|
|
25
|
+
else:
|
|
26
|
+
result_words.append("")
|
|
27
|
+
|
|
28
|
+
return " ".join(result_words)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def ext_to_lang(path: str) -> str:
|
|
32
|
+
"""Get the language from the file extension. Default to `text` if not found."""
|
|
33
|
+
MAP = {
|
|
34
|
+
"py": "python",
|
|
35
|
+
"js": "javascript",
|
|
36
|
+
"ts": "typescript",
|
|
37
|
+
"java": "java",
|
|
38
|
+
"c": "c",
|
|
39
|
+
"cpp": "cpp",
|
|
40
|
+
"cs": "csharp",
|
|
41
|
+
"html": "html",
|
|
42
|
+
"css": "css",
|
|
43
|
+
"json": "json",
|
|
44
|
+
"yaml": "yaml",
|
|
45
|
+
"yml": "yaml",
|
|
46
|
+
}
|
|
47
|
+
ext = os.path.splitext(path)[1][1:]
|
|
48
|
+
if ext in MAP:
|
|
49
|
+
return MAP[ext]
|
|
50
|
+
return "text"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def expand_file_refs(text: str) -> Tuple[str, List[str]]:
|
|
54
|
+
"""
|
|
55
|
+
Expands @-references with file or directory contents wrapped in code fences.
|
|
56
|
+
- @path/to/file.ext: Reads a single file.
|
|
57
|
+
- @path/to/dir/: Reads all files in a directory (non-recursive).
|
|
58
|
+
- @path/to/dir/**: Reads all files in a directory and its subdirectories.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
text: The input text potentially containing @-references.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
A tuple containing:
|
|
65
|
+
- Text with references replaced by file/directory contents.
|
|
66
|
+
- List of absolute paths of files that were successfully expanded.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If a referenced path does not exist.
|
|
70
|
+
"""
|
|
71
|
+
import os
|
|
72
|
+
import re
|
|
73
|
+
|
|
74
|
+
from tunacode.constants import (
|
|
75
|
+
ERROR_DIR_TOO_LARGE,
|
|
76
|
+
ERROR_DIR_TOO_MANY_FILES,
|
|
77
|
+
ERROR_FILE_NOT_FOUND,
|
|
78
|
+
MAX_FILES_IN_DIR,
|
|
79
|
+
MAX_TOTAL_DIR_SIZE,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Regex now includes trailing / and ** to capture directory intentions
|
|
83
|
+
pattern = re.compile(r"@([\w./\-_*]+)")
|
|
84
|
+
expanded_files = []
|
|
85
|
+
|
|
86
|
+
def replacer(match: re.Match) -> str:
|
|
87
|
+
path_spec = match.group(1)
|
|
88
|
+
|
|
89
|
+
is_recursive = path_spec.endswith("/**")
|
|
90
|
+
is_dir = path_spec.endswith("/")
|
|
91
|
+
|
|
92
|
+
# Determine the actual path to check on the filesystem
|
|
93
|
+
if is_recursive:
|
|
94
|
+
base_path = path_spec[:-3]
|
|
95
|
+
elif is_dir:
|
|
96
|
+
base_path = path_spec[:-1]
|
|
97
|
+
else:
|
|
98
|
+
base_path = path_spec
|
|
99
|
+
|
|
100
|
+
if not os.path.exists(base_path):
|
|
101
|
+
raise ValueError(ERROR_FILE_NOT_FOUND.format(filepath=base_path))
|
|
102
|
+
|
|
103
|
+
# For Recursive Directory Expansion ---
|
|
104
|
+
if is_recursive:
|
|
105
|
+
if not os.path.isdir(base_path):
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"Error: Path '{base_path}' for recursive expansion is not a directory."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
all_contents = [f"\n=== START RECURSIVE EXPANSION: {path_spec} ===\n"]
|
|
111
|
+
total_size, file_count = 0, 0
|
|
112
|
+
|
|
113
|
+
for root, _, filenames in os.walk(base_path):
|
|
114
|
+
for filename in filenames:
|
|
115
|
+
if file_count >= MAX_FILES_IN_DIR:
|
|
116
|
+
all_contents.append(
|
|
117
|
+
ERROR_DIR_TOO_MANY_FILES.format(path=base_path, limit=MAX_FILES_IN_DIR)
|
|
118
|
+
)
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
file_path = os.path.join(root, filename)
|
|
122
|
+
content, size = _read_and_format_file(file_path, expanded_files)
|
|
123
|
+
|
|
124
|
+
if total_size + size > MAX_TOTAL_DIR_SIZE:
|
|
125
|
+
all_contents.append(
|
|
126
|
+
ERROR_DIR_TOO_LARGE.format(
|
|
127
|
+
path=base_path, limit_mb=MAX_TOTAL_DIR_SIZE / (1024 * 1024)
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
all_contents.append(content)
|
|
133
|
+
total_size += size
|
|
134
|
+
file_count += 1
|
|
135
|
+
if file_count >= MAX_FILES_IN_DIR or total_size > MAX_TOTAL_DIR_SIZE:
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
all_contents.append(f"\n=== END RECURSIVE EXPANSION: {path_spec} ===\n")
|
|
139
|
+
return "".join(all_contents)
|
|
140
|
+
|
|
141
|
+
# For Non-Recursive Directory Expansion
|
|
142
|
+
if is_dir:
|
|
143
|
+
if not os.path.isdir(base_path):
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"Error: Path '{base_path}' for directory expansion is not a directory."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
all_contents = [f"\n=== START DIRECTORY EXPANSION: {path_spec} ===\n"]
|
|
149
|
+
total_size, file_count = 0, 0
|
|
150
|
+
|
|
151
|
+
for item_name in sorted(os.listdir(base_path)):
|
|
152
|
+
item_path = os.path.join(base_path, item_name)
|
|
153
|
+
if os.path.isfile(item_path):
|
|
154
|
+
if file_count >= MAX_FILES_IN_DIR:
|
|
155
|
+
all_contents.append(
|
|
156
|
+
ERROR_DIR_TOO_MANY_FILES.format(path=base_path, limit=MAX_FILES_IN_DIR)
|
|
157
|
+
)
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
content, size = _read_and_format_file(item_path, expanded_files)
|
|
161
|
+
if total_size + size > MAX_TOTAL_DIR_SIZE:
|
|
162
|
+
all_contents.append(
|
|
163
|
+
ERROR_DIR_TOO_LARGE.format(
|
|
164
|
+
path=base_path, limit_mb=MAX_TOTAL_DIR_SIZE / (1024 * 1024)
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
all_contents.append(content)
|
|
170
|
+
total_size += size
|
|
171
|
+
file_count += 1
|
|
172
|
+
|
|
173
|
+
all_contents.append(f"\n=== END DIRECTORY EXPANSION: {path_spec} ===\n")
|
|
174
|
+
return "".join(all_contents)
|
|
175
|
+
|
|
176
|
+
# For Single File Expansion
|
|
177
|
+
if os.path.isfile(base_path):
|
|
178
|
+
content, _ = _read_and_format_file(base_path, expanded_files)
|
|
179
|
+
return content
|
|
180
|
+
|
|
181
|
+
raise ValueError(f"Path '{base_path}' is not a valid file or directory specification.")
|
|
182
|
+
|
|
183
|
+
expanded_text = pattern.sub(replacer, text)
|
|
184
|
+
return expanded_text, list(set(expanded_files))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _read_and_format_file(file_path: str, expanded_files_tracker: List[str]) -> Tuple[str, int]:
|
|
188
|
+
"""Reads a single file, formats it, and checks size limits."""
|
|
189
|
+
from tunacode.constants import MAX_FILE_SIZE
|
|
190
|
+
|
|
191
|
+
if os.path.getsize(file_path) > MAX_FILE_SIZE:
|
|
192
|
+
# Instead of raising an error, we'll just note it and skip or process gets terminated.
|
|
193
|
+
return f"\n--- SKIPPED (too large): {file_path} ---\n", 0
|
|
194
|
+
|
|
195
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
196
|
+
content = f.read()
|
|
197
|
+
|
|
198
|
+
abs_path = os.path.abspath(file_path)
|
|
199
|
+
expanded_files_tracker.append(abs_path)
|
|
200
|
+
|
|
201
|
+
lang = ext_to_lang(file_path)
|
|
202
|
+
header = f"=== FILE REFERENCE: {file_path} ==="
|
|
203
|
+
footer = f"=== END FILE REFERENCE: {file_path} ==="
|
|
204
|
+
|
|
205
|
+
formatted_content = f"\n{header}\n```{lang}\n{content}\n```\n{footer}\n"
|
|
206
|
+
return formatted_content, len(content.encode("utf-8"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tunacode-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.40
|
|
4
4
|
Summary: Your agentic CLI developer.
|
|
5
5
|
Author-email: larock22 <noreply@github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -31,6 +31,7 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
31
31
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
32
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
33
33
|
Requires-Dist: textual-dev; extra == "dev"
|
|
34
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
34
35
|
Dynamic: license-file
|
|
35
36
|
|
|
36
37
|
# TunaCode
|
|
@@ -95,6 +95,7 @@ src/tunacode_cli.egg-info/top_level.txt
|
|
|
95
95
|
tests/conftest.py
|
|
96
96
|
tests/test_actual_parallelism.py
|
|
97
97
|
tests/test_agent_initialization.py
|
|
98
|
+
tests/test_agent_output_formatting.py
|
|
98
99
|
tests/test_background_manager.py
|
|
99
100
|
tests/test_characterization_agent_main.py
|
|
100
101
|
tests/test_characterization_bash.py
|
|
@@ -133,6 +134,7 @@ tests/test_parallel_execution_freeze_fix.py
|
|
|
133
134
|
tests/test_parallel_execution_integration.py
|
|
134
135
|
tests/test_parallel_read_only_tools.py
|
|
135
136
|
tests/test_parallel_tool_execution.py
|
|
137
|
+
tests/test_prompt_changes_validation.py
|
|
136
138
|
tests/test_read_only_confirmation.py
|
|
137
139
|
tests/test_security.py
|
|
138
140
|
tests/test_streaming_panel_tool_confirmation.py
|
|
@@ -189,6 +191,7 @@ tests/characterization/ui/test_console_output.py
|
|
|
189
191
|
tests/characterization/ui/test_diff_display.py
|
|
190
192
|
tests/characterization/ui/test_prompt_rendering.py
|
|
191
193
|
tests/characterization/ui/test_tool_confirmations.py
|
|
194
|
+
tests/characterization/utils/test_expand_file_refs.py
|
|
192
195
|
tests/characterization/utils/test_file_operations.py
|
|
193
196
|
tests/characterization/utils/test_git_commands.py
|
|
194
197
|
tests/characterization/utils/test_token_counting.py
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from tunacode.constants import (
|
|
7
|
+
MAX_FILE_SIZE,
|
|
8
|
+
MAX_FILES_IN_DIR,
|
|
9
|
+
MAX_TOTAL_DIR_SIZE,
|
|
10
|
+
)
|
|
11
|
+
from tunacode.utils.text_utils import expand_file_refs
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def setup_test_environment(tmp_path: Path, request) -> Path:
|
|
16
|
+
"""
|
|
17
|
+
Creates a temporary directory structure for testing.
|
|
18
|
+
- /README.md
|
|
19
|
+
- /src/main.py
|
|
20
|
+
- /src/components/button.js
|
|
21
|
+
- /src/components/style.css
|
|
22
|
+
- /large_file.txt (exceeds MAX_FILE_SIZE)
|
|
23
|
+
- /many_files_dir/ (contains more than MAX_FILES_IN_DIR files)
|
|
24
|
+
- /empty_dir/
|
|
25
|
+
"""
|
|
26
|
+
(tmp_path / "src" / "components").mkdir(parents=True)
|
|
27
|
+
(tmp_path / "empty_dir").mkdir()
|
|
28
|
+
|
|
29
|
+
(tmp_path / "README.md").write_text("# Project Title")
|
|
30
|
+
(tmp_path / "src/main.py").write_text("print('hello world')")
|
|
31
|
+
(tmp_path / "src/components/button.js").write_text("export const Button = () => {};")
|
|
32
|
+
(tmp_path / "src/components/style.css").write_text("button { color: blue; }")
|
|
33
|
+
|
|
34
|
+
large_file_path = tmp_path / "large_file.txt"
|
|
35
|
+
large_file_path.write_text("a" * (MAX_FILE_SIZE + 1))
|
|
36
|
+
|
|
37
|
+
many_files_dir = tmp_path / "many_files_dir"
|
|
38
|
+
many_files_dir.mkdir()
|
|
39
|
+
for i in range(MAX_FILES_IN_DIR + 5):
|
|
40
|
+
(many_files_dir / f"file_{i}.txt").write_text(f"content {i}")
|
|
41
|
+
|
|
42
|
+
original_cwd = Path.cwd()
|
|
43
|
+
os.chdir(tmp_path)
|
|
44
|
+
|
|
45
|
+
def finalizer():
|
|
46
|
+
os.chdir(original_cwd)
|
|
47
|
+
|
|
48
|
+
request.addfinalizer(finalizer)
|
|
49
|
+
|
|
50
|
+
return tmp_path
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_expand_single_file(setup_test_environment):
|
|
54
|
+
"""Tests expanding a single, valid @file reference."""
|
|
55
|
+
text = "Please review this file: @README.md"
|
|
56
|
+
expanded_text, file_list = expand_file_refs(text)
|
|
57
|
+
|
|
58
|
+
assert "=== FILE REFERENCE: README.md ===" in expanded_text
|
|
59
|
+
assert "# Project Title" in expanded_text
|
|
60
|
+
assert len(file_list) == 1
|
|
61
|
+
assert file_list[0] == str(Path("README.md").resolve())
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_expand_non_recursive_directory(setup_test_environment):
|
|
65
|
+
"""Tests expanding a directory with a trailing slash (non-recursive)."""
|
|
66
|
+
text = "What's in @src/ ?"
|
|
67
|
+
expanded_text, file_list = expand_file_refs(text)
|
|
68
|
+
|
|
69
|
+
assert "=== START DIRECTORY EXPANSION: src/ ===" in expanded_text
|
|
70
|
+
assert "=== FILE REFERENCE: src/main.py ===" in expanded_text
|
|
71
|
+
assert "print('hello world')" in expanded_text
|
|
72
|
+
assert "=== FILE REFERENCE: src/components/button.js ===" not in expanded_text
|
|
73
|
+
assert len(file_list) == 1
|
|
74
|
+
assert str(Path("src/main.py").resolve()) in file_list
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_expand_recursive_directory(setup_test_environment):
|
|
78
|
+
"""Tests expanding a directory recursively with /** syntax."""
|
|
79
|
+
text = "Review the whole codebase: @src/**"
|
|
80
|
+
expanded_text, file_list = expand_file_refs(text)
|
|
81
|
+
|
|
82
|
+
assert "=== START RECURSIVE EXPANSION: src/** ===" in expanded_text
|
|
83
|
+
assert "=== FILE REFERENCE: src/main.py ===" in expanded_text
|
|
84
|
+
assert "=== FILE REFERENCE: src/components/button.js ===" in expanded_text
|
|
85
|
+
assert "=== FILE REFERENCE: src/components/style.css ===" in expanded_text
|
|
86
|
+
assert "export const Button" in expanded_text
|
|
87
|
+
assert "button { color: blue; }" in expanded_text
|
|
88
|
+
assert len(file_list) == 3
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_no_references(setup_test_environment):
|
|
92
|
+
"""Tests that text without any @-references is returned unchanged."""
|
|
93
|
+
text = "This is a normal sentence without any file references."
|
|
94
|
+
expanded_text, file_list = expand_file_refs(text)
|
|
95
|
+
|
|
96
|
+
assert expanded_text == text
|
|
97
|
+
assert len(file_list) == 0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_multiple_mixed_references(setup_test_environment):
|
|
101
|
+
"""Tests a single prompt with both file and directory references."""
|
|
102
|
+
text = "Check the readme @README.md and all the components in @src/components/"
|
|
103
|
+
expanded_text, file_list = expand_file_refs(text)
|
|
104
|
+
assert "=== FILE REFERENCE: README.md ===" in expanded_text
|
|
105
|
+
assert "=== START DIRECTORY EXPANSION: src/components/ ===" in expanded_text
|
|
106
|
+
assert "=== FILE REFERENCE: src/components/button.js ===" in expanded_text
|
|
107
|
+
assert "=== FILE REFERENCE: src/components/style.css ===" in expanded_text
|
|
108
|
+
assert len(file_list) == 3
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_empty_directory(setup_test_environment):
|
|
112
|
+
"""Tests expanding an empty directory."""
|
|
113
|
+
text = "What is in @empty_dir/ ?"
|
|
114
|
+
expanded_text, file_list = expand_file_refs(text)
|
|
115
|
+
|
|
116
|
+
assert "=== START DIRECTORY EXPANSION: empty_dir/ ===" in expanded_text
|
|
117
|
+
assert "=== END DIRECTORY EXPANSION: empty_dir/ ===" in expanded_text
|
|
118
|
+
assert "FILE REFERENCE" not in expanded_text.split("START DIRECTORY")[1]
|
|
119
|
+
assert len(file_list) == 0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# --- Error and Limit Handling Tests ---
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_error_path_not_found(setup_test_environment):
|
|
126
|
+
"""Tests that a ValueError is raised for a non-existent path."""
|
|
127
|
+
text = "Here is a @nonexistent/file.py"
|
|
128
|
+
with pytest.raises(ValueError) as excinfo:
|
|
129
|
+
expand_file_refs(text)
|
|
130
|
+
assert "Error: File not found" in str(excinfo.value)
|
|
131
|
+
assert "nonexistent/file.py" in str(excinfo.value)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_error_path_is_file_but_used_as_dir(setup_test_environment):
|
|
135
|
+
"""Tests for ValueError when a file is referenced with directory syntax."""
|
|
136
|
+
text = "This should fail: @README.md/"
|
|
137
|
+
with pytest.raises(ValueError) as excinfo:
|
|
138
|
+
expand_file_refs(text)
|
|
139
|
+
assert "for directory expansion is not a directory" in str(excinfo.value)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_skip_file_too_large(setup_test_environment):
|
|
143
|
+
"""Tests that files exceeding MAX_FILE_SIZE are skipped."""
|
|
144
|
+
text = "Check this huge file: @large_file.txt"
|
|
145
|
+
expanded_text, file_list = expand_file_refs(text)
|
|
146
|
+
|
|
147
|
+
assert "--- SKIPPED (too large): large_file.txt ---" in expanded_text
|
|
148
|
+
assert "aaaaaaaa" not in expanded_text
|
|
149
|
+
assert len(file_list) == 0
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_limit_too_many_files_in_dir(setup_test_environment):
|
|
153
|
+
"""Tests that directory expansion stops after MAX_FILES_IN_DIR."""
|
|
154
|
+
text = "Check this huge directory: @many_files_dir/"
|
|
155
|
+
expanded_text, file_list = expand_file_refs(text)
|
|
156
|
+
assert f"Exceeds limit of {MAX_FILES_IN_DIR} files" in expanded_text
|
|
157
|
+
assert len(file_list) == MAX_FILES_IN_DIR
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_limit_total_dir_size(tmp_path):
|
|
161
|
+
"""Tests that directory expansion stops if total size is exceeded."""
|
|
162
|
+
size_test_dir = tmp_path / "size_test_dir"
|
|
163
|
+
size_test_dir.mkdir()
|
|
164
|
+
original_cwd = Path.cwd()
|
|
165
|
+
os.chdir(tmp_path)
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
file_size = MAX_TOTAL_DIR_SIZE // 2 + 1000
|
|
169
|
+
(size_test_dir / "file1.txt").write_text("b" * file_size)
|
|
170
|
+
(size_test_dir / "file2.txt").write_text("c" * file_size)
|
|
171
|
+
(size_test_dir / "file3.txt").write_text("d" * 10000)
|
|
172
|
+
text = "Check this large content dir: @size_test_dir/"
|
|
173
|
+
expanded_text, file_list = expand_file_refs(text)
|
|
174
|
+
|
|
175
|
+
assert "SKIPPED (too large)" in expanded_text
|
|
176
|
+
assert "FILE REFERENCE:" in expanded_text
|
|
177
|
+
assert len(file_list) == 1
|
|
178
|
+
finally:
|
|
179
|
+
os.chdir(original_cwd)
|