tunacode-cli 0.0.31__tar.gz → 0.0.33__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 (186) hide show
  1. {tunacode_cli-0.0.31/src/tunacode_cli.egg-info → tunacode_cli-0.0.33}/PKG-INFO +1 -1
  2. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/pyproject.toml +1 -1
  3. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/commands.py +4 -4
  4. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/repl.py +7 -3
  5. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/configuration/defaults.py +1 -1
  6. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/constants.py +1 -1
  7. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/agents/main.py +7 -2
  8. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33/src/tunacode_cli.egg-info}/PKG-INFO +1 -1
  9. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode_cli.egg-info/SOURCES.txt +4 -2
  10. tunacode_cli-0.0.33/src/tunacode_cli.egg-info/top_level.txt +1 -0
  11. tunacode_cli-0.0.33/tests/test_characterization_grep_performance.py +287 -0
  12. tunacode_cli-0.0.33/tests/test_characterization_iteration_limits.py +145 -0
  13. tunacode_cli-0.0.33/tests/test_characterization_tool_ui_behavior.py +464 -0
  14. tunacode_cli-0.0.33/tests/test_tool_handler_ui_messages.py +105 -0
  15. tunacode_cli-0.0.31/src/api/auth.py +0 -13
  16. tunacode_cli-0.0.31/src/api/users.py +0 -8
  17. tunacode_cli-0.0.31/src/tunacode_cli.egg-info/top_level.txt +0 -3
  18. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/CLAUDE.md +0 -0
  19. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/LICENSE +0 -0
  20. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/MANIFEST.in +0 -0
  21. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/README.md +0 -0
  22. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/TUNACODE.md +0 -0
  23. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/setup.cfg +0 -0
  24. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/setup.py +0 -0
  25. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/__init__.py +0 -0
  26. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/__init__.py +0 -0
  27. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/main.py +0 -0
  28. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/textual_app.py +0 -0
  29. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/textual_bridge.py +0 -0
  30. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/configuration/__init__.py +0 -0
  31. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/configuration/models.py +0 -0
  32. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/configuration/settings.py +0 -0
  33. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/context.py +0 -0
  34. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/__init__.py +0 -0
  35. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/agents/__init__.py +0 -0
  36. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/background/__init__.py +0 -0
  37. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/background/manager.py +0 -0
  38. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/code_index.py +0 -0
  39. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/llm/__init__.py +0 -0
  40. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/__init__.py +0 -0
  41. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/agent_setup.py +0 -0
  42. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/base.py +0 -0
  43. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/config_setup.py +0 -0
  44. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/coordinator.py +0 -0
  45. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/environment_setup.py +0 -0
  46. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  47. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/state.py +0 -0
  48. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/tool_handler.py +0 -0
  49. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/exceptions.py +0 -0
  50. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/prompts/system.md +0 -0
  51. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/py.typed +0 -0
  52. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/services/__init__.py +0 -0
  53. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/services/mcp.py +0 -0
  54. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/setup.py +0 -0
  55. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/__init__.py +0 -0
  56. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/base.py +0 -0
  57. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/bash.py +0 -0
  58. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/glob.py +0 -0
  59. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/grep.py +0 -0
  60. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/list_dir.py +0 -0
  61. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/read_file.py +0 -0
  62. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/read_file_async_poc.py +0 -0
  63. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/run_command.py +0 -0
  64. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/update_file.py +0 -0
  65. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/write_file.py +0 -0
  66. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/types.py +0 -0
  67. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/__init__.py +0 -0
  68. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/completers.py +0 -0
  69. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/console.py +0 -0
  70. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/constants.py +0 -0
  71. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/decorators.py +0 -0
  72. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/input.py +0 -0
  73. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/keybindings.py +0 -0
  74. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/lexers.py +0 -0
  75. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/output.py +0 -0
  76. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/panels.py +0 -0
  77. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/prompt_manager.py +0 -0
  78. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/tool_ui.py +0 -0
  79. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/validators.py +0 -0
  80. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/__init__.py +0 -0
  81. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/bm25.py +0 -0
  82. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/diff_utils.py +0 -0
  83. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/file_utils.py +0 -0
  84. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/import_cache.py +0 -0
  85. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/ripgrep.py +0 -0
  86. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/system.py +0 -0
  87. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/text_utils.py +0 -0
  88. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/token_counter.py +0 -0
  89. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/user_configuration.py +0 -0
  90. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  91. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  92. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode_cli.egg-info/requires.txt +0 -0
  93. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/__init__.py +0 -0
  94. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/conftest.py +0 -0
  95. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_agent_creation.py +0 -0
  96. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_json_tool_parsing.py +0 -0
  97. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_process_node.py +0 -0
  98. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_process_request.py +0 -0
  99. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_tool_message_patching.py +0 -0
  100. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_background_edge_cases.py +0 -0
  101. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_cleanup.py +0 -0
  102. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_task_cancellation.py +0 -0
  103. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_task_creation.py +0 -0
  104. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_task_execution.py +0 -0
  105. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_cache_management.py +0 -0
  106. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_file_scanning.py +0 -0
  107. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_index_building.py +0 -0
  108. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_search_operations.py +0 -0
  109. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_symbol_extraction.py +0 -0
  110. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/conftest.py +0 -0
  111. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_command_parsing.py +0 -0
  112. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_input_handling.py +0 -0
  113. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_keyboard_interrupts.py +0 -0
  114. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_multiline_input.py +0 -0
  115. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_repl_initialization.py +0 -0
  116. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_session_flow.py +0 -0
  117. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/services/test_error_recovery.py +0 -0
  118. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/services/test_llm_routing.py +0 -0
  119. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/services/test_mcp_integration.py +0 -0
  120. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/services/test_service_lifecycle.py +0 -0
  121. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_agent_tracking.py +0 -0
  122. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_message_history.py +0 -0
  123. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_permissions.py +0 -0
  124. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_session_management.py +0 -0
  125. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_state_initialization.py +0 -0
  126. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_user_config.py +0 -0
  127. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/test_characterization_commands.py +0 -0
  128. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_async_ui.py +0 -0
  129. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_console_output.py +0 -0
  130. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_diff_display.py +0 -0
  131. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_prompt_rendering.py +0 -0
  132. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_tool_confirmations.py +0 -0
  133. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/utils/test_file_operations.py +0 -0
  134. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/utils/test_git_commands.py +0 -0
  135. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/utils/test_token_counting.py +0 -0
  136. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/utils/test_utils_edge_cases.py +0 -0
  137. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/conftest.py +0 -0
  138. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/crud/test_core_file_operations.py +0 -0
  139. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/fixtures/__init__.py +0 -0
  140. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/fixtures/file_operations.py +0 -0
  141. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_error_recovery_flow.py +0 -0
  142. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_full_session_flow.py +0 -0
  143. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_mcp_tool_flow.py +0 -0
  144. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_multi_tool_operations.py +0 -0
  145. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_performance_scenarios.py +0 -0
  146. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_actual_parallelism.py +0 -0
  147. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_agent_initialization.py +0 -0
  148. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_background_manager.py +0 -0
  149. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_agent_main.py +0 -0
  150. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_bash.py +0 -0
  151. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_commands_system.py +0 -0
  152. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_glob.py +0 -0
  153. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_grep.py +0 -0
  154. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_list_dir.py +0 -0
  155. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_read_file.py +0 -0
  156. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_repl_utils.py +0 -0
  157. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_run_command.py +0 -0
  158. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_setup_system.py +0 -0
  159. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_update_file.py +0 -0
  160. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_utilities.py +0 -0
  161. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_write_file.py +0 -0
  162. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_cli_command_flow.py +0 -0
  163. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_cli_file_operations_integration.py +0 -0
  164. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_config_setup_async.py +0 -0
  165. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_enhanced_visual_feedback.py +0 -0
  166. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_fallback_responses.py +0 -0
  167. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_fast_glob_search.py +0 -0
  168. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_file_operations_edge_cases.py +0 -0
  169. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_file_operations_stress.py +0 -0
  170. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_file_reference_context_tracking.py +0 -0
  171. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_file_reference_expansion.py +0 -0
  172. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_grep_fast_glob.py +0 -0
  173. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_grep_legacy_compat.py +0 -0
  174. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_grep_timeout.py +0 -0
  175. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_json_tool_parsing.py +0 -0
  176. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_list_dir.py +0 -0
  177. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_execution_demo.py +0 -0
  178. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_execution_freeze_fix.py +0 -0
  179. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_execution_integration.py +0 -0
  180. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_read_only_tools.py +0 -0
  181. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_tool_execution.py +0 -0
  182. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_read_only_confirmation.py +0 -0
  183. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_tool_categorization.py +0 -0
  184. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_tool_combinations.py +0 -0
  185. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_update_command.py +0 -0
  186. {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/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.31
3
+ Version: 0.0.33
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tunacode-cli"
7
- version = "0.0.31"
7
+ version = "0.0.33"
8
8
  description = "Your agentic CLI developer."
9
9
  keywords = ["cli", "agent", "development", "automation"]
10
10
  readme = "README.md"
@@ -184,8 +184,8 @@ class IterationsCommand(SimpleCommand):
184
184
  if args:
185
185
  try:
186
186
  new_limit = int(args[0])
187
- if new_limit < 1 or new_limit > 50:
188
- await ui.error("Iterations must be between 1 and 50")
187
+ if new_limit < 1 or new_limit > 100:
188
+ await ui.error("Iterations must be between 1 and 100")
189
189
  return
190
190
 
191
191
  # Update the user config
@@ -198,9 +198,9 @@ class IterationsCommand(SimpleCommand):
198
198
  except ValueError:
199
199
  await ui.error("Please provide a valid number")
200
200
  else:
201
- current = state.user_config.get("settings", {}).get("max_iterations", 15)
201
+ current = state.user_config.get("settings", {}).get("max_iterations", 40)
202
202
  await ui.info(f"Current maximum iterations: {current}")
203
- await ui.muted("Usage: /iterations <number> (1-50)")
203
+ await ui.muted("Usage: /iterations <number> (1-100)")
204
204
 
205
205
 
206
206
  class ClearCommand(SimpleCommand):
@@ -88,12 +88,16 @@ async def _tool_confirm(tool_call, node, state_manager: StateManager):
88
88
 
89
89
  async def _tool_handler(part, node, state_manager: StateManager):
90
90
  """Handle tool execution with separated business logic and UI."""
91
- await ui.info(f"Tool({part.tool_name})")
91
+ # Create tool handler with state first to check if confirmation is needed
92
+ tool_handler = ToolHandler(state_manager)
93
+
94
+ # Only show tool info for tools that require confirmation
95
+ if tool_handler.should_confirm(part.tool_name):
96
+ await ui.info(f"Tool({part.tool_name})")
97
+
92
98
  state_manager.session.spinner.stop()
93
99
 
94
100
  try:
95
- # Create tool handler with state
96
- tool_handler = ToolHandler(state_manager)
97
101
  args = _parse_args(part.args)
98
102
 
99
103
  # Use a synchronous function in run_in_terminal to avoid async deadlocks
@@ -18,7 +18,7 @@ DEFAULT_USER_CONFIG: UserConfig = {
18
18
  },
19
19
  "settings": {
20
20
  "max_retries": 10,
21
- "max_iterations": 20,
21
+ "max_iterations": 40,
22
22
  "tool_ignore": [TOOL_READ_FILE],
23
23
  "guide_file": GUIDE_FILE_NAME,
24
24
  "fallback_response": True,
@@ -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.31"
10
+ APP_VERSION = "0.0.33"
11
11
 
12
12
  # File patterns
13
13
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -345,6 +345,11 @@ async def _process_node(
345
345
  # Check if ALL tools in this node are read-only
346
346
  all_read_only = all(part.tool_name in READ_ONLY_TOOLS for part in tool_parts)
347
347
 
348
+ # TODO: Currently only batches if ALL tools are read-only. Should be updated to use
349
+ # batch_read_only_tools() function to group consecutive read-only tools and execute
350
+ # them in parallel even when mixed with write/execute tools. For example:
351
+ # [read, read, write, read] should execute as: [read||read], [write], [read]
352
+ # instead of all sequential. The batch_read_only_tools() function exists but is unused.
348
353
  if all_read_only and len(tool_parts) > 1 and buffering_callback:
349
354
  # Execute read-only tools in parallel!
350
355
  import time
@@ -651,8 +656,8 @@ async def process_request(
651
656
  ) -> AgentRun:
652
657
  agent = get_or_create_agent(model, state_manager)
653
658
  mh = state_manager.session.messages.copy()
654
- # Get max iterations from config (default: 20)
655
- max_iterations = state_manager.session.user_config.get("settings", {}).get("max_iterations", 20)
659
+ # Get max iterations from config (default: 40)
660
+ max_iterations = state_manager.session.user_config.get("settings", {}).get("max_iterations", 40)
656
661
  fallback_enabled = state_manager.session.user_config.get("settings", {}).get(
657
662
  "fallback_response", True
658
663
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.31
3
+ Version: 0.0.33
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -5,8 +5,6 @@ README.md
5
5
  TUNACODE.md
6
6
  pyproject.toml
7
7
  setup.py
8
- src/api/auth.py
9
- src/api/users.py
10
8
  src/tunacode/__init__.py
11
9
  src/tunacode/constants.py
12
10
  src/tunacode/context.py
@@ -92,11 +90,14 @@ tests/test_characterization_bash.py
92
90
  tests/test_characterization_commands_system.py
93
91
  tests/test_characterization_glob.py
94
92
  tests/test_characterization_grep.py
93
+ tests/test_characterization_grep_performance.py
94
+ tests/test_characterization_iteration_limits.py
95
95
  tests/test_characterization_list_dir.py
96
96
  tests/test_characterization_read_file.py
97
97
  tests/test_characterization_repl_utils.py
98
98
  tests/test_characterization_run_command.py
99
99
  tests/test_characterization_setup_system.py
100
+ tests/test_characterization_tool_ui_behavior.py
100
101
  tests/test_characterization_update_file.py
101
102
  tests/test_characterization_utilities.py
102
103
  tests/test_characterization_write_file.py
@@ -123,6 +124,7 @@ tests/test_parallel_tool_execution.py
123
124
  tests/test_read_only_confirmation.py
124
125
  tests/test_tool_categorization.py
125
126
  tests/test_tool_combinations.py
127
+ tests/test_tool_handler_ui_messages.py
126
128
  tests/test_update_command.py
127
129
  tests/test_visual_parallel_feedback.py
128
130
  tests/characterization/conftest.py
@@ -0,0 +1,287 @@
1
+ """
2
+ Characterization tests for grep performance behaviors.
3
+ These tests capture the CURRENT performance characteristics and strategy selection of the grep tool.
4
+ """
5
+ import os
6
+ import sys
7
+ import tempfile
8
+ import pytest
9
+ import time
10
+ from pathlib import Path
11
+ from tunacode.tools.grep import grep
12
+
13
+ pytestmark = pytest.mark.asyncio
14
+
15
+
16
+ class TestGrepPerformanceCharacterization:
17
+ """Golden-master tests for grep performance behaviors and strategy selection."""
18
+
19
+ def setup_method(self):
20
+ """Create a temporary directory with test files."""
21
+ self.temp_dir = tempfile.mkdtemp()
22
+ self.original_cwd = os.getcwd()
23
+ os.chdir(self.temp_dir)
24
+
25
+ def teardown_method(self):
26
+ """Clean up temporary files."""
27
+ os.chdir(self.original_cwd)
28
+ import shutil
29
+ shutil.rmtree(self.temp_dir)
30
+
31
+ async def test_grep_fast_glob_prefilter_performance(self):
32
+ """Capture performance behavior with fast-glob prefiltering."""
33
+ # Create many non-matching files
34
+ for i in range(100):
35
+ await self._create_file(f"data_{i}.txt", f"data file {i}")
36
+ await self._create_file(f"log_{i}.log", f"log entry {i}")
37
+
38
+ # Create a few matching Python files
39
+ for i in range(10):
40
+ content = f"""import os
41
+ def function_{i}():
42
+ # TODO: implement feature {i}
43
+ return {i}
44
+ """
45
+ await self._create_file(f"module_{i}.py", content)
46
+
47
+ # Measure time for filtered search
48
+ start_time = time.time()
49
+ result = await grep("TODO", include_files="*.py", return_format="string")
50
+ filtered_time = time.time() - start_time
51
+
52
+ # Verify it found the matches
53
+ assert "Found" in result
54
+ assert "TODO" in result
55
+ lines = result.split("\n")
56
+
57
+ # Check for strategy information (if present)
58
+ strategy_mentioned = any("strategy" in line.lower() for line in lines)
59
+
60
+ # Filtered search should be relatively fast
61
+ assert filtered_time < 2.0 # Should complete within 2 seconds
62
+ assert isinstance(result, str)
63
+
64
+ async def test_grep_no_filter_performance(self):
65
+ """Capture performance behavior without file filtering."""
66
+ # Create many files of different types
67
+ for i in range(50):
68
+ await self._create_file(f"file_{i}.txt", f"content {i}")
69
+ await self._create_file(f"script_{i}.py", f"# Python script {i}")
70
+ await self._create_file(f"config_{i}.json", f'{{"id": {i}}}')
71
+
72
+ # Search without filter
73
+ start_time = time.time()
74
+ result = await grep("content", return_format="string")
75
+ unfiltered_time = time.time() - start_time
76
+
77
+ # Should find matches in txt files
78
+ assert "Found" in result
79
+ assert unfiltered_time < 3.0 # Reasonable time limit
80
+
81
+ async def test_grep_complex_glob_patterns(self):
82
+ """Capture behavior with complex glob patterns like *.{py,js}."""
83
+ # Create mixed file types
84
+ for i in range(20):
85
+ await self._create_file(f"module_{i}.py", f"# TODO: Python task {i}")
86
+ await self._create_file(f"script_{i}.js", f"// TODO: JavaScript task {i}")
87
+ await self._create_file(f"data_{i}.txt", f"TODO: Text task {i}")
88
+
89
+ # Search with complex glob
90
+ result = await grep("TODO", include_files="*.{py,js}", return_format="string")
91
+
92
+ # Should find matches in both .py and .js files
93
+ assert "Found" in result
94
+ lines = result.split("\n")
95
+
96
+ # Check if both file types are included
97
+ py_found = any(".py" in line for line in lines)
98
+ js_found = any(".js" in line for line in lines)
99
+ txt_found = any(".txt" in line for line in lines)
100
+
101
+ assert py_found or js_found # At least one should be found
102
+ assert not txt_found # .txt files should be excluded
103
+
104
+ async def test_grep_max_glob_limit(self):
105
+ """Capture behavior when hitting MAX_GLOB limit."""
106
+ # Create more files than MAX_GLOB limit (assuming it's around 1000)
107
+ for i in range(200): # Create 200 Python files
108
+ await self._create_file(f"large_set_{i}.py", f"# File {i}\n# SEARCH_ME")
109
+
110
+ # Try to search with glob filter
111
+ result = await grep("SEARCH_ME", include_files="*.py", return_format="string")
112
+
113
+ # Should still work but might use different strategy
114
+ assert "Found" in result or "SEARCH_ME" in result
115
+
116
+ # Check if strategy information mentions limits
117
+ lines = result.split("\n")
118
+ strategy_line = next((line for line in lines if "strategy" in line.lower()), "")
119
+
120
+ # Capture actual behavior
121
+ assert isinstance(result, str)
122
+
123
+ async def test_grep_regex_with_prefilter(self):
124
+ """Capture performance with regex patterns and file filtering."""
125
+ # Create test files
126
+ for i in range(30):
127
+ content = f"""import os
128
+ import sys
129
+ from pathlib import Path
130
+
131
+ def process_{i}():
132
+ return Path('/tmp/file_{i}')
133
+ """
134
+ await self._create_file(f"processor_{i}.py", content)
135
+ await self._create_file(f"readme_{i}.md", f"# Documentation {i}\nUse Path objects")
136
+
137
+ # Regex search with filter
138
+ start_time = time.time()
139
+ result = await grep(r"import.*Path", use_regex=True, include_files="*.py", return_format="string")
140
+ regex_time = time.time() - start_time
141
+
142
+ # Should find matches efficiently
143
+ assert "Found" in result
144
+ assert regex_time < 2.0
145
+
146
+ # Should only match Python files
147
+ lines = result.split("\n")
148
+ assert not any(".md" in line for line in lines)
149
+
150
+ async def test_grep_deeply_nested_structure(self):
151
+ """Capture behavior with deeply nested directory structures."""
152
+ # Create nested directories
153
+ nested_path = Path(".")
154
+ for i in range(5): # 5 levels deep
155
+ nested_path = nested_path / f"level_{i}"
156
+ nested_path.mkdir(exist_ok=True)
157
+ await self._create_file(str(nested_path / f"file_{i}.py"), f"# NESTED_PATTERN at level {i}")
158
+
159
+ # Search in nested structure
160
+ result = await grep("NESTED_PATTERN", return_format="list")
161
+
162
+ # Should find files at all levels
163
+ assert len(result) >= 5
164
+
165
+ # Check if deeply nested files are found
166
+ deepest_found = any("level_4" in f for f in result)
167
+ assert deepest_found
168
+
169
+ async def test_grep_mixed_encodings(self):
170
+ """Capture behavior with files of different encodings."""
171
+ # Create files with different content types
172
+ await self._create_file("utf8.txt", "UTF-8: Hello 世界")
173
+ await self._create_file("ascii.txt", "ASCII: Hello World")
174
+
175
+ # Some files might have encoding issues
176
+ Path("latin1.txt").write_bytes("Latin-1: café".encode('latin-1'))
177
+
178
+ # Search for common pattern
179
+ result = await grep("Hello", return_format="list")
180
+
181
+ # Should handle UTF-8 and ASCII files
182
+ assert len(result) >= 2
183
+ assert any("utf8.txt" in f for f in result)
184
+ assert any("ascii.txt" in f for f in result)
185
+
186
+ async def test_grep_symlinks(self):
187
+ """Capture behavior with symbolic links."""
188
+ # Create a real file
189
+ await self._create_file("original.py", "# SYMLINK_TEST pattern")
190
+
191
+ # Create a symlink (if supported by OS)
192
+ try:
193
+ Path("link_to_original.py").symlink_to("original.py")
194
+ has_symlink = True
195
+ except (OSError, NotImplementedError):
196
+ has_symlink = False
197
+
198
+ # Search for pattern
199
+ result = await grep("SYMLINK_TEST", return_format="list")
200
+
201
+ if has_symlink:
202
+ # Capture whether symlinks are followed or not
203
+ assert len(result) >= 1 # At least the original
204
+ # Record actual behavior regarding symlinks
205
+ else:
206
+ assert len(result) == 1 # Just the original
207
+
208
+ async def test_grep_performance_with_large_matches(self):
209
+ """Capture behavior when many files match the pattern."""
210
+ # Create many files with the same pattern
211
+ for i in range(100):
212
+ await self._create_file(f"match_{i}.txt", f"Line 1\nCOMMON_PATTERN here\nLine 3")
213
+
214
+ # Time the search
215
+ start_time = time.time()
216
+ result = await grep("COMMON_PATTERN", return_format="string")
217
+ many_matches_time = time.time() - start_time
218
+
219
+ # Should complete in reasonable time even with many matches
220
+ assert many_matches_time < 5.0
221
+ assert "Found" in result
222
+
223
+ # Check if result mentions match count
224
+ lines = result.split("\n")
225
+ first_line = lines[0] if lines else ""
226
+ assert "100" in first_line or "matches" in first_line.lower()
227
+
228
+ async def test_grep_empty_directory(self):
229
+ """Capture behavior in empty directories."""
230
+ # Create empty subdirectory
231
+ Path("empty_dir").mkdir()
232
+
233
+ # Search in empty directory
234
+ result = await grep("pattern", path="empty_dir", return_format="list")
235
+
236
+ # Should handle gracefully
237
+ assert isinstance(result, list)
238
+ assert len(result) == 0
239
+
240
+ async def test_grep_performance_first_match(self):
241
+ """Capture first-match deadline behavior."""
242
+ # Create files where pattern appears later
243
+ for i in range(50):
244
+ # Pattern is not in first files
245
+ content = f"File {i} content\n" * 100 if i < 40 else f"File {i}\nTARGET_PATTERN\nMore content"
246
+ await self._create_file(f"delayed_{i}.txt", content)
247
+
248
+ # Search should respect deadline
249
+ start_time = time.time()
250
+ result = await grep("TARGET_PATTERN", return_format="string")
251
+ search_time = time.time() - start_time
252
+
253
+ # Should find matches (eventually)
254
+ assert "Found" in result or "TARGET_PATTERN" in result
255
+
256
+ # Search time might be affected by 3-second deadline
257
+ assert search_time < 10.0 # Should not take too long
258
+
259
+ async def test_grep_glob_prefilter_file_limit(self):
260
+ """Capture behavior of MAX_GLOB limit affecting strategy."""
261
+ # Create exactly at the boundary of MAX_GLOB limit
262
+ # Assuming MAX_GLOB is 1000 based on the code
263
+ for i in range(1010): # Slightly over limit
264
+ await self._create_file(f"boundary_{i}.py", f"# Test file {i}")
265
+
266
+ # Add one file with our pattern
267
+ await self._create_file("target.py", "# BOUNDARY_TEST pattern")
268
+
269
+ # Search with glob that would match all Python files
270
+ result = await grep("BOUNDARY_TEST", include_files="*.py", return_format="string")
271
+
272
+ # Should still find the pattern
273
+ assert "Found" in result or "BOUNDARY_TEST" in result
274
+
275
+ # Check if strategy differs due to file count
276
+ lines = result.split("\n")
277
+ strategy_info = [line for line in lines if "strategy" in line.lower()]
278
+
279
+ # Capture actual strategy selection behavior
280
+ assert isinstance(result, str)
281
+
282
+ # Helper method
283
+ async def _create_file(self, filename: str, content: str) -> None:
284
+ """Helper to create a file with content."""
285
+ path = Path(filename)
286
+ path.parent.mkdir(parents=True, exist_ok=True)
287
+ path.write_text(content)
@@ -0,0 +1,145 @@
1
+ """
2
+ Characterization tests for iteration limits.
3
+
4
+ These tests capture the CURRENT behavior of iteration limits in TunaCode.
5
+ This is a golden-master test that documents the existing behavior before
6
+ we make changes to update the defaults.
7
+
8
+ Current behavior captured:
9
+ - Default max_iterations: 40 (updated from 20)
10
+ - Command limit: 100 (updated from 50)
11
+ - Fallback on line 201 in commands.py shows 40 (was 15, now updated)
12
+ """
13
+
14
+ import pytest
15
+ from unittest.mock import Mock, patch, AsyncMock
16
+
17
+ from tunacode.configuration.defaults import DEFAULT_USER_CONFIG
18
+ from tunacode.cli.commands import IterationsCommand
19
+ from tunacode.types import CommandContext
20
+ from tunacode.core.state import StateManager
21
+
22
+ class TestIterationLimitsCharacterization:
23
+ """Golden-master tests for iteration limit behavior."""
24
+
25
+ def test_default_max_iterations_is_40(self):
26
+ """Capture behavior: default max_iterations is set to 40 in DEFAULT_USER_CONFIG."""
27
+ assert DEFAULT_USER_CONFIG["settings"]["max_iterations"] == 40
28
+
29
+ @pytest.mark.asyncio
30
+ async def test_iterations_command_max_limit_is_100(self):
31
+ """Capture behavior: /iterations command enforces maximum limit of 100."""
32
+ # Setup
33
+ cmd = IterationsCommand()
34
+ state_manager = Mock(spec=StateManager)
35
+ state_manager.session = Mock()
36
+ state_manager.session.user_config = {"settings": {}}
37
+
38
+ context = CommandContext(
39
+ state_manager=state_manager
40
+ )
41
+
42
+ # Mock UI functions
43
+ with patch("tunacode.cli.commands.ui.error") as mock_error:
44
+ # Test trying to set iterations to 101 (above limit)
45
+ await cmd.execute(["101"], context)
46
+
47
+ # Assert error was shown
48
+ mock_error.assert_called_once_with("Iterations must be between 1 and 100")
49
+
50
+ @pytest.mark.asyncio
51
+ async def test_iterations_command_accepts_100(self):
52
+ """Capture behavior: /iterations command accepts exactly 100 as valid."""
53
+ # Setup
54
+ cmd = IterationsCommand()
55
+ state_manager = Mock(spec=StateManager)
56
+ state_manager.session = Mock()
57
+ state_manager.session.user_config = {"settings": {}}
58
+
59
+ context = CommandContext(
60
+ state_manager=state_manager
61
+ )
62
+
63
+ # Mock UI functions
64
+ with patch("tunacode.cli.commands.ui.success") as mock_success:
65
+ with patch("tunacode.cli.commands.ui.muted") as mock_muted:
66
+ # Test setting iterations to 100 (at limit)
67
+ await cmd.execute(["100"], context)
68
+
69
+ # Assert success was shown
70
+ mock_success.assert_called_once_with("Maximum iterations set to 100")
71
+ mock_muted.assert_called_once_with("Higher values allow more complex reasoning but may be slower")
72
+
73
+ # Assert value was set
74
+ assert state_manager.session.user_config["settings"]["max_iterations"] == 100
75
+
76
+ @pytest.mark.asyncio
77
+ async def test_iterations_command_rejects_0_and_negative(self):
78
+ """Capture behavior: /iterations command rejects 0 and negative values."""
79
+ # Setup
80
+ cmd = IterationsCommand()
81
+ state_manager = Mock(spec=StateManager)
82
+ state_manager.session = Mock()
83
+ state_manager.session.user_config = {"settings": {}}
84
+
85
+ context = CommandContext(
86
+ state_manager=state_manager
87
+ )
88
+
89
+ # Mock UI functions
90
+ with patch("tunacode.cli.commands.ui.error") as mock_error:
91
+ # Test 0
92
+ await cmd.execute(["0"], context)
93
+ mock_error.assert_called_with("Iterations must be between 1 and 100")
94
+
95
+ # Reset mock
96
+ mock_error.reset_mock()
97
+
98
+ # Test negative
99
+ await cmd.execute(["-5"], context)
100
+ mock_error.assert_called_with("Iterations must be between 1 and 100")
101
+
102
+ @pytest.mark.asyncio
103
+ async def test_iterations_command_display_shows_default(self):
104
+ """Capture behavior: /iterations command shows default of 40 when no config exists."""
105
+ # Setup
106
+ cmd = IterationsCommand()
107
+ state_manager = Mock(spec=StateManager)
108
+ state_manager.session = Mock()
109
+ state_manager.session.user_config = {} # No settings
110
+
111
+ context = CommandContext(
112
+ state_manager=state_manager
113
+ )
114
+
115
+ # Mock UI functions
116
+ with patch("tunacode.cli.commands.ui.info") as mock_info:
117
+ with patch("tunacode.cli.commands.ui.muted") as mock_muted:
118
+ # Test showing current value without args
119
+ await cmd.execute([], context)
120
+
121
+ # Assert shows 40 as default (matching DEFAULT_USER_CONFIG)
122
+ mock_info.assert_called_once_with("Current maximum iterations: 40")
123
+ mock_muted.assert_called_once_with("Usage: /iterations <number> (1-100)")
124
+
125
+ def test_process_request_reads_max_iterations_from_config(self):
126
+ """Document behavior: process_request reads max_iterations from config on line 660."""
127
+ # This is a documentation test to capture where max_iterations is read
128
+ # in the process_request function. The actual line is:
129
+ # max_iterations = state_manager.session.user_config.get("settings", {}).get("max_iterations", 40)
130
+
131
+ # The process_request function:
132
+ # 1. Reads max_iterations from user config settings
133
+ # 2. Falls back to 40 if not specified
134
+ # 3. Uses this value to limit iteration count in the agent loop
135
+ # 4. Breaks the loop when i >= max_iterations
136
+ pass
137
+
138
+ def test_main_agent_reads_max_iterations_on_line_660(self):
139
+ """Document that main.py reads max_iterations from config on line 660."""
140
+ # This is a documentation test to capture the specific line where
141
+ # max_iterations is read. The actual line is:
142
+ # max_iterations = state_manager.session.user_config.get("settings", {}).get("max_iterations", 20)
143
+
144
+ # This confirms the fallback default is 20 when not specified in config
145
+ pass