tunacode-cli 0.0.39__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.

Files changed (209) hide show
  1. {tunacode_cli-0.0.39/src/tunacode_cli.egg-info → tunacode_cli-0.0.40}/PKG-INFO +2 -1
  2. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/pyproject.toml +2 -1
  3. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/constants.py +11 -1
  4. tunacode_cli-0.0.40/src/tunacode/utils/text_utils.py +206 -0
  5. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40/src/tunacode_cli.egg-info}/PKG-INFO +2 -1
  6. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/SOURCES.txt +1 -0
  7. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/requires.txt +1 -0
  8. tunacode_cli-0.0.40/tests/characterization/utils/test_expand_file_refs.py +179 -0
  9. tunacode_cli-0.0.39/src/tunacode/utils/text_utils.py +0 -100
  10. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/CLAUDE.md +0 -0
  11. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/LICENSE +0 -0
  12. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/MANIFEST.in +0 -0
  13. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/README.md +0 -0
  14. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/TUNACODE.md +0 -0
  15. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/setup.cfg +0 -0
  16. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/setup.py +0 -0
  17. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/__init__.py +0 -0
  18. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/__init__.py +0 -0
  19. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/__init__.py +0 -0
  20. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/base.py +0 -0
  21. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/__init__.py +0 -0
  22. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/conversation.py +0 -0
  23. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/debug.py +0 -0
  24. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/development.py +0 -0
  25. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/model.py +0 -0
  26. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/implementations/system.py +0 -0
  27. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/commands/registry.py +0 -0
  28. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/main.py +0 -0
  29. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/repl.py +0 -0
  30. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/textual_app.py +0 -0
  31. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/cli/textual_bridge.py +0 -0
  32. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/configuration/__init__.py +0 -0
  33. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/configuration/defaults.py +0 -0
  34. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/configuration/models.py +0 -0
  35. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/configuration/settings.py +0 -0
  36. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/context.py +0 -0
  37. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/__init__.py +0 -0
  38. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/agents/__init__.py +0 -0
  39. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/agents/main.py +0 -0
  40. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/agents/utils.py +0 -0
  41. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/background/__init__.py +0 -0
  42. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/background/manager.py +0 -0
  43. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/code_index.py +0 -0
  44. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/llm/__init__.py +0 -0
  45. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/setup/__init__.py +0 -0
  46. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/setup/agent_setup.py +0 -0
  47. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/setup/base.py +0 -0
  48. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/setup/config_setup.py +0 -0
  49. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/setup/coordinator.py +0 -0
  50. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/setup/environment_setup.py +0 -0
  51. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  52. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/state.py +0 -0
  53. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/core/tool_handler.py +0 -0
  54. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/exceptions.py +0 -0
  55. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/prompts/system.md +0 -0
  56. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/py.typed +0 -0
  57. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/services/__init__.py +0 -0
  58. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/services/mcp.py +0 -0
  59. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/setup.py +0 -0
  60. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/__init__.py +0 -0
  61. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/base.py +0 -0
  62. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/bash.py +0 -0
  63. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/glob.py +0 -0
  64. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/grep.py +0 -0
  65. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/list_dir.py +0 -0
  66. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/read_file.py +0 -0
  67. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/read_file_async_poc.py +0 -0
  68. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/run_command.py +0 -0
  69. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/update_file.py +0 -0
  70. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/tools/write_file.py +0 -0
  71. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/types.py +0 -0
  72. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/__init__.py +0 -0
  73. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/completers.py +0 -0
  74. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/console.py +0 -0
  75. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/constants.py +0 -0
  76. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/decorators.py +0 -0
  77. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/input.py +0 -0
  78. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/keybindings.py +0 -0
  79. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/lexers.py +0 -0
  80. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/output.py +0 -0
  81. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/panels.py +0 -0
  82. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/prompt_manager.py +0 -0
  83. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/tool_ui.py +0 -0
  84. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/utils.py +0 -0
  85. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/ui/validators.py +0 -0
  86. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/__init__.py +0 -0
  87. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/bm25.py +0 -0
  88. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/diff_utils.py +0 -0
  89. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/file_utils.py +0 -0
  90. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/import_cache.py +0 -0
  91. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/ripgrep.py +0 -0
  92. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/security.py +0 -0
  93. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/system.py +0 -0
  94. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/token_counter.py +0 -0
  95. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode/utils/user_configuration.py +0 -0
  96. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  97. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  98. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/src/tunacode_cli.egg-info/top_level.txt +0 -0
  99. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/agent/__init__.py +0 -0
  100. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/agent/conftest.py +0 -0
  101. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/agent/test_agent_creation.py +0 -0
  102. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/agent/test_json_tool_parsing.py +0 -0
  103. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/agent/test_process_node.py +0 -0
  104. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/agent/test_process_request.py +0 -0
  105. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/agent/test_tool_message_patching.py +0 -0
  106. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/background/test_background_edge_cases.py +0 -0
  107. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/background/test_cleanup.py +0 -0
  108. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/background/test_task_cancellation.py +0 -0
  109. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/background/test_task_creation.py +0 -0
  110. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/background/test_task_execution.py +0 -0
  111. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_cache_management.py +0 -0
  112. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_file_scanning.py +0 -0
  113. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_index_building.py +0 -0
  114. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_search_operations.py +0 -0
  115. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/code_index/test_symbol_extraction.py +0 -0
  116. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/commands/__init__.py +0 -0
  117. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/commands/test_init_command.py +0 -0
  118. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/conftest.py +0 -0
  119. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/context/__init__.py +0 -0
  120. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/context/test_context_acceptance.py +0 -0
  121. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/context/test_context_integration.py +0 -0
  122. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/context/test_context_loading.py +0 -0
  123. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/context/test_tunacode_logging.py +0 -0
  124. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/repl/test_command_parsing.py +0 -0
  125. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/repl/test_input_handling.py +0 -0
  126. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/repl/test_keyboard_interrupts.py +0 -0
  127. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/repl/test_multiline_input.py +0 -0
  128. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/repl/test_repl_initialization.py +0 -0
  129. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/repl/test_session_flow.py +0 -0
  130. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/services/test_error_recovery.py +0 -0
  131. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/services/test_llm_routing.py +0 -0
  132. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/services/test_mcp_integration.py +0 -0
  133. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/services/test_service_lifecycle.py +0 -0
  134. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/state/test_agent_tracking.py +0 -0
  135. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/state/test_message_history.py +0 -0
  136. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/state/test_permissions.py +0 -0
  137. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/state/test_session_management.py +0 -0
  138. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/state/test_state_initialization.py +0 -0
  139. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/state/test_user_config.py +0 -0
  140. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/test_characterization_commands.py +0 -0
  141. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/ui/test_async_ui.py +0 -0
  142. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/ui/test_console_output.py +0 -0
  143. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/ui/test_diff_display.py +0 -0
  144. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/ui/test_prompt_rendering.py +0 -0
  145. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/ui/test_tool_confirmations.py +0 -0
  146. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/utils/test_file_operations.py +0 -0
  147. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/utils/test_git_commands.py +0 -0
  148. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/utils/test_token_counting.py +0 -0
  149. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/characterization/utils/test_utils_edge_cases.py +0 -0
  150. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/conftest.py +0 -0
  151. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/crud/test_core_file_operations.py +0 -0
  152. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/fixtures/__init__.py +0 -0
  153. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/fixtures/file_operations.py +0 -0
  154. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/integration/test_error_recovery_flow.py +0 -0
  155. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/integration/test_full_session_flow.py +0 -0
  156. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/integration/test_mcp_tool_flow.py +0 -0
  157. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/integration/test_multi_tool_operations.py +0 -0
  158. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/integration/test_performance_scenarios.py +0 -0
  159. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_actual_parallelism.py +0 -0
  160. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_agent_initialization.py +0 -0
  161. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_agent_output_formatting.py +0 -0
  162. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_background_manager.py +0 -0
  163. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_agent_main.py +0 -0
  164. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_bash.py +0 -0
  165. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_commands_system.py +0 -0
  166. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_glob.py +0 -0
  167. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_grep.py +0 -0
  168. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_grep_performance.py +0 -0
  169. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_iteration_limits.py +0 -0
  170. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_list_dir.py +0 -0
  171. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_read_file.py +0 -0
  172. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_repl_utils.py +0 -0
  173. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_run_command.py +0 -0
  174. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_setup_system.py +0 -0
  175. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_tool_ui_behavior.py +0 -0
  176. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_update_file.py +0 -0
  177. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_utilities.py +0 -0
  178. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_characterization_write_file.py +0 -0
  179. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_cli_command_flow.py +0 -0
  180. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_cli_file_operations_integration.py +0 -0
  181. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_config_directory_creation.py +0 -0
  182. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_config_setup_async.py +0 -0
  183. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_enhanced_visual_feedback.py +0 -0
  184. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_fallback_responses.py +0 -0
  185. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_fast_glob_search.py +0 -0
  186. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_file_operations_edge_cases.py +0 -0
  187. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_file_operations_stress.py +0 -0
  188. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_file_reference_context_tracking.py +0 -0
  189. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_file_reference_expansion.py +0 -0
  190. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_grep_fast_glob.py +0 -0
  191. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_grep_legacy_compat.py +0 -0
  192. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_grep_timeout.py +0 -0
  193. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_json_tool_parsing.py +0 -0
  194. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_list_dir.py +0 -0
  195. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_parallel_execution_demo.py +0 -0
  196. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_parallel_execution_freeze_fix.py +0 -0
  197. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_parallel_execution_integration.py +0 -0
  198. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_parallel_read_only_tools.py +0 -0
  199. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_parallel_tool_execution.py +0 -0
  200. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_prompt_changes_validation.py +0 -0
  201. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_read_only_confirmation.py +0 -0
  202. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_security.py +0 -0
  203. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_streaming_panel_tool_confirmation.py +0 -0
  204. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_streaming_spinner_conflict.py +0 -0
  205. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_tool_categorization.py +0 -0
  206. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_tool_combinations.py +0 -0
  207. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_tool_handler_ui_messages.py +0 -0
  208. {tunacode_cli-0.0.39 → tunacode_cli-0.0.40}/tests/test_update_command.py +0 -0
  209. {tunacode_cli-0.0.39 → 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.39
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.39"
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.39"
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."
@@ -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.39
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
@@ -191,6 +191,7 @@ tests/characterization/ui/test_console_output.py
191
191
  tests/characterization/ui/test_diff_display.py
192
192
  tests/characterization/ui/test_prompt_rendering.py
193
193
  tests/characterization/ui/test_tool_confirmations.py
194
+ tests/characterization/utils/test_expand_file_refs.py
194
195
  tests/characterization/utils/test_file_operations.py
195
196
  tests/characterization/utils/test_git_commands.py
196
197
  tests/characterization/utils/test_token_counting.py
@@ -11,3 +11,4 @@ pytest
11
11
  pytest-cov
12
12
  pytest-asyncio
13
13
  textual-dev
14
+ pre-commit
@@ -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)
@@ -1,100 +0,0 @@
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
- """Expand @file references with file contents wrapped in code fences.
55
-
56
- Args:
57
- text: The input text potentially containing @file references.
58
-
59
- Returns:
60
- Tuple[str, List[str]]: A tuple containing:
61
- - Text with any @file references replaced by the file's contents
62
- - List of absolute paths of files that were successfully expanded
63
-
64
- Raises:
65
- ValueError: If a referenced file does not exist or is too large.
66
- """
67
- import os
68
- import re
69
-
70
- from tunacode.constants import (
71
- ERROR_FILE_NOT_FOUND,
72
- ERROR_FILE_TOO_LARGE,
73
- MAX_FILE_SIZE,
74
- MSG_FILE_SIZE_LIMIT,
75
- )
76
-
77
- pattern = re.compile(r"@([\w./_-]+)")
78
- expanded_files = []
79
-
80
- def replacer(match: re.Match) -> str:
81
- path = match.group(1)
82
- if not os.path.exists(path):
83
- raise ValueError(ERROR_FILE_NOT_FOUND.format(filepath=path))
84
-
85
- if os.path.getsize(path) > MAX_FILE_SIZE:
86
- raise ValueError(ERROR_FILE_TOO_LARGE.format(filepath=path) + MSG_FILE_SIZE_LIMIT)
87
-
88
- with open(path, "r", encoding="utf-8") as f:
89
- content = f.read()
90
-
91
- # Track the absolute path of the file
92
- abs_path = os.path.abspath(path)
93
- expanded_files.append(abs_path)
94
-
95
- lang = ext_to_lang(path)
96
- # Add clear headers to indicate this is a file reference, not code to write
97
- return f"\n=== FILE REFERENCE: {path} ===\n```{lang}\n{content}\n```\n=== END FILE REFERENCE: {path} ===\n"
98
-
99
- expanded_text = pattern.sub(replacer, text)
100
- return expanded_text, expanded_files
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes