shotgun-sh 0.3.1.dev1__tar.gz → 0.3.3.dev2__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.
Files changed (231) hide show
  1. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/PKG-INFO +2 -2
  2. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/pyproject.toml +2 -2
  3. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/agent_manager.py +116 -0
  4. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/common.py +17 -71
  5. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/config/models.py +36 -27
  6. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/config/provider.py +0 -2
  7. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/file_content_deduplication.py +66 -43
  8. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/file_management.py +4 -1
  9. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/web_search/gemini.py +1 -3
  10. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +25 -0
  11. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/plan.j2 +16 -0
  12. shotgun_sh-0.3.3.dev2/src/shotgun/prompts/agents/research.j2 +107 -0
  13. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/specify.j2 +54 -1
  14. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/state/system_state.j2 +1 -8
  15. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/tasks.j2 +16 -0
  16. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat/chat_screen.py +25 -0
  17. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/model_picker.py +14 -7
  18. shotgun_sh-0.3.1.dev1/src/shotgun/prompts/agents/research.j2 +0 -66
  19. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/.gitignore +0 -0
  20. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/LICENSE +0 -0
  21. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/README.md +0 -0
  22. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/hatch_build.py +0 -0
  23. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/__init__.py +0 -0
  24. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/__init__.py +0 -0
  25. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/config/README.md +0 -0
  26. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/config/__init__.py +0 -0
  27. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/config/constants.py +0 -0
  28. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/config/manager.py +0 -0
  29. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/config/streaming_test.py +0 -0
  30. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/context_analyzer/__init__.py +0 -0
  31. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/context_analyzer/analyzer.py +0 -0
  32. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/context_analyzer/constants.py +0 -0
  33. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/context_analyzer/formatter.py +0 -0
  34. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/context_analyzer/models.py +0 -0
  35. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/__init__.py +0 -0
  36. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/filters.py +0 -0
  37. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/__init__.py +0 -0
  38. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/chunking.py +0 -0
  39. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/compaction.py +0 -0
  40. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/constants.py +0 -0
  41. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/context_extraction.py +0 -0
  42. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/history_building.py +0 -0
  43. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/history_processors.py +0 -0
  44. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/message_utils.py +0 -0
  45. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/token_counting/__init__.py +0 -0
  46. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/token_counting/anthropic.py +0 -0
  47. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/token_counting/base.py +0 -0
  48. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/token_counting/openai.py +0 -0
  49. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/token_counting/sentencepiece_counter.py +0 -0
  50. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/token_counting/tokenizer_cache.py +0 -0
  51. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/token_counting/utils.py +0 -0
  52. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/history/token_estimation.py +0 -0
  53. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/manager.py +0 -0
  54. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/conversation/models.py +0 -0
  55. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/error/__init__.py +0 -0
  56. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/error/models.py +0 -0
  57. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/export.py +0 -0
  58. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/llm.py +0 -0
  59. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/messages.py +0 -0
  60. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/models.py +0 -0
  61. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/plan.py +0 -0
  62. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/research.py +0 -0
  63. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/runner.py +0 -0
  64. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/specify.py +0 -0
  65. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tasks.py +0 -0
  66. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/__init__.py +0 -0
  67. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  68. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  69. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  70. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  71. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/codebase/models.py +0 -0
  72. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  73. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  74. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/registry.py +0 -0
  75. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  76. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  77. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  78. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  79. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/agents/usage_manager.py +0 -0
  80. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/api_endpoints.py +0 -0
  81. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/build_constants.py +0 -0
  82. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/__init__.py +0 -0
  83. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/clear.py +0 -0
  84. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/codebase/__init__.py +0 -0
  85. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/codebase/commands.py +0 -0
  86. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/codebase/models.py +0 -0
  87. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/compact.py +0 -0
  88. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/config.py +0 -0
  89. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/context.py +0 -0
  90. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/error_handler.py +0 -0
  91. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/export.py +0 -0
  92. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/feedback.py +0 -0
  93. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/models.py +0 -0
  94. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/plan.py +0 -0
  95. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/research.py +0 -0
  96. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/spec/__init__.py +0 -0
  97. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/spec/backup.py +0 -0
  98. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/spec/commands.py +0 -0
  99. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/spec/models.py +0 -0
  100. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/spec/pull_service.py +0 -0
  101. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/specify.py +0 -0
  102. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/tasks.py +0 -0
  103. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/update.py +0 -0
  104. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/cli/utils.py +0 -0
  105. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/__init__.py +0 -0
  106. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/core/__init__.py +0 -0
  107. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/core/change_detector.py +0 -0
  108. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  109. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/core/cypher_models.py +0 -0
  110. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/core/ingestor.py +0 -0
  111. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/core/language_config.py +0 -0
  112. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/core/manager.py +0 -0
  113. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/core/nl_query.py +0 -0
  114. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/core/parser_loader.py +0 -0
  115. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/models.py +0 -0
  116. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/codebase/service.py +0 -0
  117. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/exceptions.py +0 -0
  118. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/llm_proxy/__init__.py +0 -0
  119. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/llm_proxy/client.py +0 -0
  120. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/llm_proxy/clients.py +0 -0
  121. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/llm_proxy/constants.py +0 -0
  122. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/llm_proxy/models.py +0 -0
  123. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/logging_config.py +0 -0
  124. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/main.py +0 -0
  125. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/posthog_telemetry.py +0 -0
  126. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/__init__.py +0 -0
  127. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/__init__.py +0 -0
  128. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/export.j2 +0 -0
  129. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  130. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  131. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  132. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  133. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/codebase/__init__.py +0 -0
  134. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  135. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  136. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  137. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  138. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  139. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  140. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/history/__init__.py +0 -0
  141. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/history/chunk_summarization.j2 +0 -0
  142. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/history/combine_summaries.j2 +0 -0
  143. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  144. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/history/summarization.j2 +0 -0
  145. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/loader.py +0 -0
  146. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/prompts/tools/web_search.j2 +0 -0
  147. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/py.typed +0 -0
  148. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/sdk/__init__.py +0 -0
  149. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/sdk/codebase.py +0 -0
  150. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/sdk/exceptions.py +0 -0
  151. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/sdk/models.py +0 -0
  152. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/sdk/services.py +0 -0
  153. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/sentry_telemetry.py +0 -0
  154. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/settings.py +0 -0
  155. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/__init__.py +0 -0
  156. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/client.py +0 -0
  157. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/constants.py +0 -0
  158. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/exceptions.py +0 -0
  159. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/models.py +0 -0
  160. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/shared_specs/__init__.py +0 -0
  161. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/shared_specs/file_scanner.py +0 -0
  162. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/shared_specs/hasher.py +0 -0
  163. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/shared_specs/models.py +0 -0
  164. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/shared_specs/upload_pipeline.py +0 -0
  165. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/shared_specs/utils.py +0 -0
  166. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/specs_client.py +0 -0
  167. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/shotgun_web/supabase_client.py +0 -0
  168. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/telemetry.py +0 -0
  169. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/__init__.py +0 -0
  170. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/app.py +0 -0
  171. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/commands/__init__.py +0 -0
  172. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/components/context_indicator.py +0 -0
  173. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/components/mode_indicator.py +0 -0
  174. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/components/prompt_input.py +0 -0
  175. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/components/spinner.py +0 -0
  176. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/components/splash.py +0 -0
  177. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/components/status_bar.py +0 -0
  178. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/components/vertical_tail.py +0 -0
  179. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/containers.py +0 -0
  180. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/dependencies.py +0 -0
  181. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/filtered_codebase_service.py +0 -0
  182. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/layout.py +0 -0
  183. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/protocols.py +0 -0
  184. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat/__init__.py +0 -0
  185. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat/chat.tcss +0 -0
  186. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat/codebase_index_prompt_screen.py +0 -0
  187. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat/codebase_index_selection.py +0 -0
  188. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat/help_text.py +0 -0
  189. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat/prompt_history.py +0 -0
  190. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat.tcss +0 -0
  191. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  192. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
  193. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  194. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat_screen/history/__init__.py +0 -0
  195. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat_screen/history/agent_response.py +0 -0
  196. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat_screen/history/chat_history.py +0 -0
  197. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat_screen/history/formatters.py +0 -0
  198. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat_screen/history/partial_response.py +0 -0
  199. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/chat_screen/history/user_question.py +0 -0
  200. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/confirmation_dialog.py +0 -0
  201. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/directory_setup.py +0 -0
  202. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/feedback.py +0 -0
  203. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/github_issue.py +0 -0
  204. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/onboarding.py +0 -0
  205. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/pipx_migration.py +0 -0
  206. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/provider_config.py +0 -0
  207. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/shared_specs/__init__.py +0 -0
  208. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/shared_specs/create_spec_dialog.py +0 -0
  209. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/shared_specs/models.py +0 -0
  210. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/shared_specs/share_specs_dialog.py +0 -0
  211. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/shared_specs/upload_progress_screen.py +0 -0
  212. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/shotgun_auth.py +0 -0
  213. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/spec_pull.py +0 -0
  214. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/splash.py +0 -0
  215. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/screens/welcome.py +0 -0
  216. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/services/__init__.py +0 -0
  217. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/services/conversation_service.py +0 -0
  218. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/state/__init__.py +0 -0
  219. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/state/processing_state.py +0 -0
  220. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/styles.tcss +0 -0
  221. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/utils/__init__.py +0 -0
  222. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/utils/mode_progress.py +0 -0
  223. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/widgets/__init__.py +0 -0
  224. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/tui/widgets/widget_coordinator.py +0 -0
  225. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/utils/__init__.py +0 -0
  226. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/utils/datetime_utils.py +0 -0
  227. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/utils/env_utils.py +0 -0
  228. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/utils/file_system_utils.py +0 -0
  229. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/utils/marketing.py +0 -0
  230. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/utils/source_detection.py +0 -0
  231. {shotgun_sh-0.3.1.dev1 → shotgun_sh-0.3.3.dev2}/src/shotgun/utils/update_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgun-sh
3
- Version: 0.3.1.dev1
3
+ Version: 0.3.3.dev2
4
4
  Summary: AI-powered research, planning, and task management CLI tool
5
5
  Project-URL: Homepage, https://shotgun.sh/
6
6
  Project-URL: Repository, https://github.com/shotgun-sh/shotgun
@@ -32,7 +32,7 @@ Requires-Dist: logfire>=2.0.0
32
32
  Requires-Dist: openai>=1.0.0
33
33
  Requires-Dist: packaging>=23.0
34
34
  Requires-Dist: posthog>=3.0.0
35
- Requires-Dist: pydantic-ai>=0.0.14
35
+ Requires-Dist: pydantic-ai>=1.26.0
36
36
  Requires-Dist: pydantic-settings>=2.0.0
37
37
  Requires-Dist: pyperclip>=1.10.0
38
38
  Requires-Dist: rich>=13.0.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.3.1.dev1"
3
+ version = "0.3.3.dev2"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -24,7 +24,7 @@ classifiers = [
24
24
  dependencies = [
25
25
  "typer>=0.12.0",
26
26
  "rich>=13.0.0",
27
- "pydantic-ai>=0.0.14",
27
+ "pydantic-ai>=1.26.0",
28
28
  "pydantic-settings>=2.0.0",
29
29
  "httpx>=0.27.0",
30
30
  "jinja2>=3.1.0",
@@ -38,6 +38,7 @@ from pydantic_ai.messages import (
38
38
  PartDeltaEvent,
39
39
  PartStartEvent,
40
40
  SystemPromptPart,
41
+ TextPartDelta,
41
42
  ToolCallPart,
42
43
  ToolCallPartDelta,
43
44
  UserPromptPart,
@@ -174,6 +175,67 @@ class CompactionCompletedMessage(Message):
174
175
  """Event posted when conversation compaction completes."""
175
176
 
176
177
 
178
+ class ToolExecutionStartedMessage(Message):
179
+ """Event posted when a tool starts executing.
180
+
181
+ This allows the UI to update the spinner text to provide feedback
182
+ during long-running tool executions.
183
+ """
184
+
185
+ def __init__(self, spinner_text: str = "Processing...") -> None:
186
+ """Initialize the tool execution started message.
187
+
188
+ Args:
189
+ spinner_text: The spinner message to display
190
+ """
191
+ super().__init__()
192
+ self.spinner_text = spinner_text
193
+
194
+
195
+ class ToolStreamingProgressMessage(Message):
196
+ """Event posted during tool call streaming to show progress.
197
+
198
+ This provides visual feedback while tool arguments are streaming,
199
+ especially useful for long-running writes like file content.
200
+ """
201
+
202
+ def __init__(self, streamed_tokens: int, spinner_text: str) -> None:
203
+ """Initialize the tool streaming progress message.
204
+
205
+ Args:
206
+ streamed_tokens: Approximate number of tokens streamed so far
207
+ spinner_text: The current spinner message to preserve
208
+ """
209
+ super().__init__()
210
+ self.streamed_tokens = streamed_tokens
211
+ self.spinner_text = spinner_text
212
+
213
+
214
+ # Fun spinner messages to show during tool execution
215
+ SPINNER_MESSAGES = [
216
+ "Pontificating...",
217
+ "Ruminating...",
218
+ "Cogitating...",
219
+ "Deliberating...",
220
+ "Contemplating...",
221
+ "Reticulating splines...",
222
+ "Consulting the oracle...",
223
+ "Gathering thoughts...",
224
+ "Processing neurons...",
225
+ "Summoning wisdom...",
226
+ "Brewing ideas...",
227
+ "Polishing pixels...",
228
+ "Herding electrons...",
229
+ "Warming up the flux capacitor...",
230
+ "Consulting ancient tomes...",
231
+ "Channeling the muses...",
232
+ "Percolating possibilities...",
233
+ "Untangling complexity...",
234
+ "Shuffling priorities...",
235
+ "Aligning the stars...",
236
+ ]
237
+
238
+
177
239
  class AgentStreamingStarted(Message):
178
240
  """Event posted when agent starts streaming responses."""
179
241
 
@@ -210,6 +272,11 @@ class _PartialStreamState:
210
272
 
211
273
  messages: list[ModelRequest | ModelResponse] = field(default_factory=list)
212
274
  current_response: ModelResponse | None = None
275
+ # Token counting for tool call streaming progress
276
+ streamed_tokens: int = 0
277
+ current_spinner_text: str = "Processing..."
278
+ # Track last reported tokens to throttle UI updates
279
+ last_reported_tokens: int = 0
213
280
 
214
281
 
215
282
  class AgentManager(Widget):
@@ -992,6 +1059,44 @@ class AgentManager(Widget):
992
1059
  )
993
1060
  continue
994
1061
 
1062
+ # Count tokens from the delta for progress indication
1063
+ delta_len = 0
1064
+ is_tool_call_delta = False
1065
+ if isinstance(event.delta, ToolCallPartDelta):
1066
+ is_tool_call_delta = True
1067
+ # args_delta can be str or dict depending on provider
1068
+ args_delta = event.delta.args_delta
1069
+ if isinstance(args_delta, str):
1070
+ delta_len = len(args_delta)
1071
+ elif isinstance(args_delta, dict):
1072
+ # For dict deltas, estimate from JSON representation
1073
+ delta_len = len(json.dumps(args_delta))
1074
+ # Pick a spinner message when tool streaming starts
1075
+ if state.current_spinner_text == "Processing...":
1076
+ import random
1077
+
1078
+ state.current_spinner_text = random.choice( # noqa: S311
1079
+ SPINNER_MESSAGES
1080
+ )
1081
+ elif isinstance(event.delta, TextPartDelta):
1082
+ delta_len = len(event.delta.content_delta)
1083
+
1084
+ if delta_len > 0:
1085
+ # Approximate tokens: len / 4 is a rough estimate
1086
+ state.streamed_tokens += delta_len // 4 + 1
1087
+ # Send progress update for tool call streaming
1088
+ # Throttle updates to every ~75 tokens to avoid flooding UI
1089
+ if is_tool_call_delta and (
1090
+ state.streamed_tokens - state.last_reported_tokens >= 75
1091
+ ):
1092
+ state.last_reported_tokens = state.streamed_tokens
1093
+ self.post_message(
1094
+ ToolStreamingProgressMessage(
1095
+ state.streamed_tokens,
1096
+ state.current_spinner_text,
1097
+ )
1098
+ )
1099
+
995
1100
  try:
996
1101
  updated_part = event.delta.apply(
997
1102
  cast(ModelResponsePart, partial_parts[index])
@@ -1087,6 +1192,17 @@ class AgentManager(Widget):
1087
1192
  if partial_message is not None:
1088
1193
  state.current_response = partial_message
1089
1194
  self._post_partial_message(False)
1195
+
1196
+ # Notify UI that a tool is about to execute
1197
+ # This updates the spinner with a fun message during tool execution
1198
+ # Pick a random spinner message and store it for progress updates
1199
+ import random
1200
+
1201
+ spinner_text = random.choice(SPINNER_MESSAGES) # noqa: S311
1202
+ state.current_spinner_text = spinner_text
1203
+ state.streamed_tokens = 0 # Reset token count for new tool
1204
+ self.post_message(ToolExecutionStartedMessage(spinner_text))
1205
+
1090
1206
  elif isinstance(event, FunctionToolResultEvent):
1091
1207
  # Track tool completion event
1092
1208
 
@@ -38,7 +38,6 @@ from .tools import (
38
38
  retrieve_code,
39
39
  write_file,
40
40
  )
41
- from .tools.file_management import AGENT_DIRECTORIES
42
41
 
43
42
  logger = get_logger(__name__)
44
43
 
@@ -352,86 +351,33 @@ async def extract_markdown_toc(agent_mode: AgentType | None) -> str | None:
352
351
 
353
352
 
354
353
  def get_agent_existing_files(agent_mode: AgentType | None = None) -> list[str]:
355
- """Get list of existing files for the given agent mode.
354
+ """Get list of all existing files in .shotgun directory.
355
+
356
+ All agents can read any file in .shotgun/, so we list all files regardless
357
+ of agent mode. This includes user-added files that agents should be aware of.
356
358
 
357
359
  Args:
358
- agent_mode: The agent mode to check files for. If None, lists all files.
360
+ agent_mode: Unused, kept for backwards compatibility.
359
361
 
360
362
  Returns:
361
363
  List of existing file paths relative to .shotgun directory
362
364
  """
363
365
  base_path = get_shotgun_base_path()
364
- existing_files = []
365
-
366
- # If no agent mode, list all files in base path and first level subdirectories
367
- if agent_mode is None:
368
- # List files in the root .shotgun directory
369
- for item in base_path.iterdir():
370
- if item.is_file():
371
- existing_files.append(item.name)
372
- elif item.is_dir():
373
- # List files in first-level subdirectories
374
- for subitem in item.iterdir():
375
- if subitem.is_file():
376
- relative_path = subitem.relative_to(base_path)
377
- existing_files.append(str(relative_path))
366
+ existing_files: list[str] = []
367
+
368
+ if not base_path.exists():
378
369
  return existing_files
379
370
 
380
- # Handle specific agent modes
381
- if agent_mode not in AGENT_DIRECTORIES:
382
- return []
383
-
384
- if agent_mode == AgentType.EXPORT:
385
- # For export agent, list all files in exports directory
386
- exports_dir = base_path / "exports"
387
- if exports_dir.exists():
388
- for file_path in exports_dir.rglob("*"):
389
- if file_path.is_file():
390
- relative_path = file_path.relative_to(base_path)
371
+ # List all files in .shotgun directory and subdirectories
372
+ for item in base_path.iterdir():
373
+ if item.is_file():
374
+ existing_files.append(item.name)
375
+ elif item.is_dir():
376
+ # List files in subdirectories (one level deep to avoid too much noise)
377
+ for subitem in item.iterdir():
378
+ if subitem.is_file():
379
+ relative_path = subitem.relative_to(base_path)
391
380
  existing_files.append(str(relative_path))
392
- else:
393
- # For other agents, check files/directories they have access to
394
- allowed_paths_raw = AGENT_DIRECTORIES[agent_mode]
395
-
396
- # Convert single Path/string to list of Paths for uniform handling
397
- if isinstance(allowed_paths_raw, str):
398
- # Special case: "*" means export agent (shouldn't reach here but handle it)
399
- allowed_paths = (
400
- [Path(allowed_paths_raw)] if allowed_paths_raw != "*" else []
401
- )
402
- elif isinstance(allowed_paths_raw, Path):
403
- allowed_paths = [allowed_paths_raw]
404
- else:
405
- # Already a list
406
- allowed_paths = allowed_paths_raw
407
-
408
- # Check each allowed path
409
- for allowed_path in allowed_paths:
410
- allowed_str = str(allowed_path)
411
-
412
- # Check if it's a directory (no .md suffix)
413
- if not allowed_path.suffix or not allowed_str.endswith(".md"):
414
- # It's a directory - list all files within it
415
- dir_path = base_path / allowed_str
416
- if dir_path.exists() and dir_path.is_dir():
417
- for file_path in dir_path.rglob("*"):
418
- if file_path.is_file():
419
- relative_path = file_path.relative_to(base_path)
420
- existing_files.append(str(relative_path))
421
- else:
422
- # It's a file - check if it exists
423
- file_path = base_path / allowed_str
424
- if file_path.exists():
425
- existing_files.append(allowed_str)
426
-
427
- # Also check for associated directory (e.g., research/ for research.md)
428
- base_name = allowed_str.replace(".md", "")
429
- dir_path = base_path / base_name
430
- if dir_path.exists() and dir_path.is_dir():
431
- for file_path in dir_path.rglob("*"):
432
- if file_path.is_file():
433
- relative_path = file_path.relative_to(base_path)
434
- existing_files.append(str(relative_path))
435
381
 
436
382
  return existing_files
437
383
 
@@ -25,16 +25,17 @@ class KeyProvider(StrEnum):
25
25
  class ModelName(StrEnum):
26
26
  """Available AI model names."""
27
27
 
28
- GPT_5 = "gpt-5"
29
- GPT_5_MINI = "gpt-5-mini"
30
28
  GPT_5_1 = "gpt-5.1"
31
29
  GPT_5_1_CODEX = "gpt-5.1-codex"
32
30
  GPT_5_1_CODEX_MINI = "gpt-5.1-codex-mini"
33
- CLAUDE_OPUS_4_1 = "claude-opus-4-1"
31
+ CLAUDE_OPUS_4_5 = "claude-opus-4-5"
32
+ CLAUDE_SONNET_4 = "claude-sonnet-4"
34
33
  CLAUDE_SONNET_4_5 = "claude-sonnet-4-5"
35
34
  CLAUDE_HAIKU_4_5 = "claude-haiku-4-5"
36
35
  GEMINI_2_5_PRO = "gemini-2.5-pro"
37
36
  GEMINI_2_5_FLASH = "gemini-2.5-flash"
37
+ GEMINI_2_5_FLASH_LITE = "gemini-2.5-flash-lite"
38
+ GEMINI_3_PRO_PREVIEW = "gemini-3-pro-preview"
38
39
 
39
40
 
40
41
  class ModelSpec(BaseModel):
@@ -101,22 +102,6 @@ class ModelConfig(BaseModel):
101
102
 
102
103
  # Model specifications registry (static metadata)
103
104
  MODEL_SPECS: dict[ModelName, ModelSpec] = {
104
- ModelName.GPT_5: ModelSpec(
105
- name=ModelName.GPT_5,
106
- provider=ProviderType.OPENAI,
107
- max_input_tokens=400_000,
108
- max_output_tokens=128_000,
109
- litellm_proxy_model_name="openai/gpt-5",
110
- short_name="GPT-5",
111
- ),
112
- ModelName.GPT_5_MINI: ModelSpec(
113
- name=ModelName.GPT_5_MINI,
114
- provider=ProviderType.OPENAI,
115
- max_input_tokens=400_000,
116
- max_output_tokens=128_000,
117
- litellm_proxy_model_name="openai/gpt-5-mini",
118
- short_name="GPT-5 Mini",
119
- ),
120
105
  ModelName.GPT_5_1: ModelSpec(
121
106
  name=ModelName.GPT_5_1,
122
107
  provider=ProviderType.OPENAI,
@@ -141,14 +126,6 @@ MODEL_SPECS: dict[ModelName, ModelSpec] = {
141
126
  litellm_proxy_model_name="openai/gpt-5.1-codex-mini",
142
127
  short_name="GPT-5.1 Codex Mini",
143
128
  ),
144
- ModelName.CLAUDE_OPUS_4_1: ModelSpec(
145
- name=ModelName.CLAUDE_OPUS_4_1,
146
- provider=ProviderType.ANTHROPIC,
147
- max_input_tokens=200_000,
148
- max_output_tokens=32_000,
149
- litellm_proxy_model_name="anthropic/claude-opus-4-1",
150
- short_name="Opus 4.1",
151
- ),
152
129
  ModelName.CLAUDE_SONNET_4_5: ModelSpec(
153
130
  name=ModelName.CLAUDE_SONNET_4_5,
154
131
  provider=ProviderType.ANTHROPIC,
@@ -181,6 +158,38 @@ MODEL_SPECS: dict[ModelName, ModelSpec] = {
181
158
  litellm_proxy_model_name="gemini/gemini-2.5-flash",
182
159
  short_name="Gemini 2.5 Flash",
183
160
  ),
161
+ ModelName.CLAUDE_OPUS_4_5: ModelSpec(
162
+ name=ModelName.CLAUDE_OPUS_4_5,
163
+ provider=ProviderType.ANTHROPIC,
164
+ max_input_tokens=200_000,
165
+ max_output_tokens=64_000,
166
+ litellm_proxy_model_name="anthropic/claude-opus-4-5",
167
+ short_name="Opus 4.5",
168
+ ),
169
+ ModelName.CLAUDE_SONNET_4: ModelSpec(
170
+ name=ModelName.CLAUDE_SONNET_4,
171
+ provider=ProviderType.ANTHROPIC,
172
+ max_input_tokens=200_000,
173
+ max_output_tokens=64_000,
174
+ litellm_proxy_model_name="anthropic/claude-sonnet-4",
175
+ short_name="Sonnet 4",
176
+ ),
177
+ ModelName.GEMINI_2_5_FLASH_LITE: ModelSpec(
178
+ name=ModelName.GEMINI_2_5_FLASH_LITE,
179
+ provider=ProviderType.GOOGLE,
180
+ max_input_tokens=1_048_576,
181
+ max_output_tokens=65_536,
182
+ litellm_proxy_model_name="gemini/gemini-2.5-flash-lite",
183
+ short_name="Gemini 2.5 Flash Lite",
184
+ ),
185
+ ModelName.GEMINI_3_PRO_PREVIEW: ModelSpec(
186
+ name=ModelName.GEMINI_3_PRO_PREVIEW,
187
+ provider=ProviderType.GOOGLE,
188
+ max_input_tokens=1_048_576,
189
+ max_output_tokens=65_536,
190
+ litellm_proxy_model_name="gemini/gemini-3-pro-preview",
191
+ short_name="Gemini 3 Pro",
192
+ ),
184
193
  }
185
194
 
186
195
 
@@ -271,8 +271,6 @@ async def get_provider_model(
271
271
  # Check and test streaming capability for GPT-5 family models
272
272
  supports_streaming = True # Default to True for all models
273
273
  if model_name in (
274
- ModelName.GPT_5,
275
- ModelName.GPT_5_MINI,
276
274
  ModelName.GPT_5_1,
277
275
  ModelName.GPT_5_1_CODEX,
278
276
  ModelName.GPT_5_1_CODEX_MINI,
@@ -5,8 +5,6 @@ tool returns before LLM-based compaction. Files are still accessible via
5
5
  `retrieve_code` (codebase) or `read_file` (.shotgun/ folder).
6
6
  """
7
7
 
8
- import copy
9
- import re
10
8
  from enum import StrEnum
11
9
  from typing import Any
12
10
 
@@ -43,40 +41,46 @@ SHOTGUN_PLACEHOLDER = (
43
41
  "**Content**: [Removed for compaction - file persisted in .shotgun/ folder]"
44
42
  )
45
43
 
46
- # Pattern for parsing file_read output (codebase files)
47
- # Format: **File**: `path`\n**Size**: N bytes\n[optional encoding]\n\n**Content**:\n```lang\ncontent```
48
- CODEBASE_FILE_PATTERN = re.compile(
49
- r"\*\*File\*\*:\s*`([^`]+)`\s*\n" # File path
50
- r"\*\*Size\*\*:\s*(\d+)\s*bytes\s*\n" # Size in bytes
51
- r"(?:\*\*Encoding\*\*:.*?\n)?" # Optional encoding line
52
- r"\n\*\*Content\*\*:\s*\n" # Blank line + Content header
53
- r"```(\w*)\n" # Language tag
54
- r"(.*?)```", # Actual content
55
- re.DOTALL,
56
- )
44
+ # Simple prefix for detecting file_read output format
45
+ # Instead of using regex, we just check for the expected prefix and extract the file path
46
+ CODEBASE_FILE_PREFIX = "**File**: `"
47
+
57
48
 
49
+ def _extract_file_path(content: str) -> str | None:
50
+ """Extract file path from file_read tool return content.
58
51
 
59
- def _parse_codebase_file_content(
60
- content: str,
61
- ) -> tuple[str, int, str, str] | None:
62
- """Parse file_read tool return content.
52
+ Uses simple string operations instead of regex for maximum performance.
53
+ The file_read tool output format is: **File**: `path`\\n...
63
54
 
64
55
  Args:
65
56
  content: The tool return content string
66
57
 
67
58
  Returns:
68
- Tuple of (file_path, size_bytes, language, actual_content) or None if not parseable
59
+ The file path or None if format doesn't match
69
60
  """
70
- match = CODEBASE_FILE_PATTERN.search(content)
71
- if not match:
61
+ # Fast check: content must start with expected prefix
62
+ if not content.startswith(CODEBASE_FILE_PREFIX):
63
+ return None
64
+
65
+ # Find the closing backtick after the prefix
66
+ prefix_len = len(CODEBASE_FILE_PREFIX)
67
+ backtick_pos = content.find("`", prefix_len)
68
+
69
+ if backtick_pos == -1:
72
70
  return None
73
71
 
74
- file_path = match.group(1)
75
- size_bytes = int(match.group(2))
76
- language = match.group(3) or ""
77
- actual_content = match.group(4)
72
+ return content[prefix_len:backtick_pos]
73
+
74
+
75
+ def _get_language_from_path(file_path: str) -> str:
76
+ """Infer programming language from file extension."""
77
+ from pathlib import Path
78
+
79
+ from shotgun.codebase.core.language_config import get_language_config
78
80
 
79
- return file_path, size_bytes, language, actual_content
81
+ ext = Path(file_path).suffix
82
+ config = get_language_config(ext)
83
+ return config.name if config else "unknown"
80
84
 
81
85
 
82
86
  def _create_codebase_placeholder(file_path: str, size_bytes: int, language: str) -> str:
@@ -110,6 +114,11 @@ def deduplicate_file_content(
110
114
  This is a deterministic pre-compaction pass that reduces tokens without
111
115
  requiring an LLM. Files remain accessible via their respective tools.
112
116
 
117
+ This function uses copy-on-write semantics: only messages that need
118
+ modification are copied, while unmodified messages are reused by reference.
119
+ This significantly reduces memory allocation and processing time for large
120
+ conversations where only a subset of messages contain file content.
121
+
113
122
  Args:
114
123
  messages: Conversation history
115
124
  retention_window: Keep full content in last N messages (for recent context)
@@ -120,15 +129,17 @@ def deduplicate_file_content(
120
129
  if not messages:
121
130
  return messages, 0
122
131
 
123
- # Deep copy to avoid modifying original
124
- modified_messages = copy.deepcopy(messages)
125
132
  total_tokens_saved = 0
126
133
  files_deduplicated = 0
127
134
 
128
135
  # Calculate retention boundary (keep last N messages intact)
129
- retention_start = max(0, len(modified_messages) - retention_window)
136
+ retention_start = max(0, len(messages) - retention_window)
137
+
138
+ # Track which message indices need replacement
139
+ # We use a dict to store index -> new_message mappings
140
+ replacements: dict[int, ModelMessage] = {}
130
141
 
131
- for msg_idx, message in enumerate(modified_messages):
142
+ for msg_idx, message in enumerate(messages):
132
143
  # Skip messages in retention window
133
144
  if msg_idx >= retention_start:
134
145
  continue
@@ -159,18 +170,18 @@ def deduplicate_file_content(
159
170
 
160
171
  # Handle codebase file reads (file_read)
161
172
  if tool_name == FileReadTool.CODEBASE:
162
- parsed = _parse_codebase_file_content(content)
163
- if parsed:
164
- file_path, size_bytes, language, actual_content = parsed
165
- # Only replace if actual content is substantial
166
- if len(actual_content) >= MIN_CONTENT_LENGTH:
167
- replacement = _create_codebase_placeholder(
168
- file_path, size_bytes, language
169
- )
170
- logger.debug(
171
- f"Deduplicating codebase file: {file_path} "
172
- f"({size_bytes} bytes)"
173
- )
173
+ file_path = _extract_file_path(content)
174
+ if file_path:
175
+ # Use content length as size estimate (includes formatting overhead
176
+ # but close enough for deduplication purposes)
177
+ size_bytes = len(content)
178
+ language = _get_language_from_path(file_path)
179
+ replacement = _create_codebase_placeholder(
180
+ file_path, size_bytes, language
181
+ )
182
+ logger.debug(
183
+ f"Deduplicating codebase file: {file_path} ({size_bytes} bytes)"
184
+ )
174
185
 
175
186
  # Handle .shotgun/ file reads (read_file)
176
187
  elif tool_name == FileReadTool.SHOTGUN_FOLDER:
@@ -203,9 +214,21 @@ def deduplicate_file_content(
203
214
  else:
204
215
  new_parts.append(part)
205
216
 
206
- # Replace message with new parts if modified
217
+ # Only create a new message if parts were actually modified
207
218
  if message_modified:
208
- modified_messages[msg_idx] = ModelRequest(parts=new_parts)
219
+ replacements[msg_idx] = ModelRequest(parts=new_parts)
220
+
221
+ # If no modifications were made, return original list (no allocation needed)
222
+ if not replacements:
223
+ return messages, 0
224
+
225
+ # Build result list with copy-on-write: reuse unmodified messages
226
+ modified_messages: list[ModelMessage] = []
227
+ for idx, msg in enumerate(messages):
228
+ if idx in replacements:
229
+ modified_messages.append(replacements[idx])
230
+ else:
231
+ modified_messages.append(msg)
209
232
 
210
233
  if files_deduplicated > 0:
211
234
  logger.info(
@@ -23,7 +23,10 @@ logger = get_logger(__name__)
23
23
  # - A list of Paths: multiple allowed files/directories (e.g., [Path("specification.md"), Path("contracts")])
24
24
  # - "*": any file except protected files (for export agent)
25
25
  AGENT_DIRECTORIES: dict[AgentType, str | Path | list[Path]] = {
26
- AgentType.RESEARCH: Path("research.md"),
26
+ AgentType.RESEARCH: [
27
+ Path("research.md"),
28
+ Path("research"),
29
+ ], # Research can write main file and research folder
27
30
  AgentType.SPECIFY: [
28
31
  Path("specification.md"),
29
32
  Path("contracts"),
@@ -1,7 +1,7 @@
1
1
  """Gemini web search tool implementation."""
2
2
 
3
3
  from opentelemetry import trace
4
- from pydantic_ai.messages import ModelMessage, ModelRequest
4
+ from pydantic_ai.messages import ModelMessage, ModelRequest, TextPart
5
5
  from pydantic_ai.settings import ModelSettings
6
6
 
7
7
  from shotgun.agents.config import get_provider_model
@@ -82,8 +82,6 @@ async def gemini_web_search_tool(query: str) -> str:
82
82
  )
83
83
 
84
84
  # Extract text from response
85
- from pydantic_ai.messages import TextPart
86
-
87
85
  result_text = "No content returned from search"
88
86
  if response.parts:
89
87
  for part in response.parts:
@@ -4,6 +4,31 @@ Your extensive expertise spans, among other things:
4
4
  * Software Architecture
5
5
  * Software Development
6
6
 
7
+ ## YOUR ROLE IN THE PIPELINE
8
+
9
+ **CRITICAL**: You are a DOCUMENTATION and PLANNING agent, NOT a coding/implementation agent.
10
+
11
+ - You produce DOCUMENTS (research, specifications, plans, tasks) that AI coding agents will consume
12
+ - You do NOT write production code, implement features, or make code changes
13
+ - NEVER offer to "move forward with implementation" or "start coding" - that's not your job
14
+ - NEVER ask "would you like me to implement this?" - implementation is done by separate AI coding tools
15
+ - Your deliverable is always a document file (.md), not code execution
16
+ - When your work is complete, the user will take your documents to a coding agent (Claude Code, Cursor, etc.)
17
+
18
+ ## AGENT FILE PERMISSIONS
19
+
20
+ There are four agents in the pipeline, and each agent can ONLY write to specific files. The user can switch between agents using **Shift+Tab**.
21
+
22
+ The **Research agent** can only write to `research.md` and files inside the `.shotgun/research/` directory. If the user asks about specifications, plans, or tasks, tell them: "Use **Shift+Tab** to switch to the [appropriate] agent which can edit that file for you."
23
+
24
+ The **Specification agent** can only write to `specification.md` and files inside the `.shotgun/contracts/` directory. If the user asks about research, plans, or tasks, tell them which agent handles that file.
25
+
26
+ The **Plan agent** can only write to `plan.md`. If the user asks about research, specifications, or tasks, tell them which agent handles that file.
27
+
28
+ The **Tasks agent** can only write to `tasks.md`. If the user asks about research, specifications, or plans, tell them which agent handles that file.
29
+
30
+ When a user asks you to edit a file you cannot write to, you MUST tell them which agent can help and how to switch: "I can't edit [filename] - that's handled by the [agent name] agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
31
+
7
32
  ## KEY RULES
8
33
 
9
34
  {% if interactive_mode %}
@@ -6,6 +6,22 @@ Your job is to help create comprehensive, actionable plans for software projects
6
6
 
7
7
  {% include 'agents/partials/common_agent_system_prompt.j2' %}
8
8
 
9
+ ## YOUR SCOPE AND HANDOFFS
10
+
11
+ You are the **Plan agent**. Your file is `plan.md` - this is the ONLY file you can write to.
12
+
13
+ When your plan is complete, suggest the next step:
14
+ "I've completed the plan. Use **Shift+Tab** to switch to the tasks agent to break this plan into actionable tasks."
15
+
16
+ If the user asks you to edit other files, redirect them helpfully:
17
+ - For research.md: "I can't edit research.md - that's handled by the research agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
18
+ - For specification.md or contracts: "I can't edit specification.md - that's handled by the specification agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
19
+ - For tasks.md: "I can't edit tasks.md - that's handled by the tasks agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
20
+
21
+ NEVER offer to do work outside your scope:
22
+ - Don't offer to write research, specifications, or tasks - redirect the user to the appropriate agent
23
+ - Don't offer to implement code - you are not a coding agent
24
+
9
25
  ## MEMORY MANAGEMENT PROTOCOL
10
26
 
11
27
  - You have exclusive write access to: `plan.md`