shotgun-sh 0.2.6.dev5__tar.gz → 0.2.7.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.

Potentially problematic release.


This version of shotgun-sh might be problematic. Click here for more details.

Files changed (161) hide show
  1. shotgun_sh-0.2.7.dev2/PKG-INFO +126 -0
  2. shotgun_sh-0.2.7.dev2/README_PYPI.md +71 -0
  3. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/pyproject.toml +4 -2
  4. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/agent_manager.py +272 -14
  5. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/common.py +42 -17
  6. shotgun_sh-0.2.7.dev2/src/shotgun/agents/conversation_history.py +227 -0
  7. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/conversation_manager.py +24 -2
  8. shotgun_sh-0.2.7.dev2/src/shotgun/agents/history/context_extraction.py +195 -0
  9. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/file_management.py +55 -9
  10. shotgun_sh-0.2.7.dev2/src/shotgun/prompts/agents/specify.j2 +318 -0
  11. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat.py +54 -13
  12. shotgun_sh-0.2.6.dev5/PKG-INFO +0 -467
  13. shotgun_sh-0.2.6.dev5/src/shotgun/agents/conversation_history.py +0 -106
  14. shotgun_sh-0.2.6.dev5/src/shotgun/agents/history/context_extraction.py +0 -108
  15. shotgun_sh-0.2.6.dev5/src/shotgun/prompts/agents/specify.j2 +0 -51
  16. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/.gitignore +0 -0
  17. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/LICENSE +0 -0
  18. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/README.md +0 -0
  19. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/hatch_build.py +0 -0
  20. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/__init__.py +0 -0
  21. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/__init__.py +0 -0
  22. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/__init__.py +0 -0
  23. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/constants.py +0 -0
  24. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/manager.py +0 -0
  25. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/models.py +0 -0
  26. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/provider.py +0 -0
  27. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/export.py +0 -0
  28. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/__init__.py +0 -0
  29. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/compaction.py +0 -0
  30. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/constants.py +0 -0
  31. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/history_building.py +0 -0
  32. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/history_processors.py +0 -0
  33. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/message_utils.py +0 -0
  34. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/__init__.py +0 -0
  35. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/anthropic.py +0 -0
  36. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/base.py +0 -0
  37. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/openai.py +0 -0
  38. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -0
  39. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/tokenizer_cache.py +0 -0
  40. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/utils.py +0 -0
  41. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_estimation.py +0 -0
  42. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/llm.py +0 -0
  43. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/messages.py +0 -0
  44. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/models.py +0 -0
  45. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/plan.py +0 -0
  46. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/research.py +0 -0
  47. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/specify.py +0 -0
  48. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tasks.py +0 -0
  49. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/__init__.py +0 -0
  50. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  51. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  52. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  53. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  54. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/models.py +0 -0
  55. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  56. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  57. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  58. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  59. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  60. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  61. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  62. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/usage_manager.py +0 -0
  63. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/api_endpoints.py +0 -0
  64. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/build_constants.py +0 -0
  65. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/__init__.py +0 -0
  66. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/codebase/__init__.py +0 -0
  67. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/codebase/commands.py +0 -0
  68. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/codebase/models.py +0 -0
  69. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/config.py +0 -0
  70. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/export.py +0 -0
  71. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/feedback.py +0 -0
  72. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/models.py +0 -0
  73. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/plan.py +0 -0
  74. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/research.py +0 -0
  75. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/specify.py +0 -0
  76. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/tasks.py +0 -0
  77. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/update.py +0 -0
  78. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/utils.py +0 -0
  79. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/__init__.py +0 -0
  80. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/__init__.py +0 -0
  81. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/change_detector.py +0 -0
  82. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  83. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/cypher_models.py +0 -0
  84. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/ingestor.py +0 -0
  85. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/language_config.py +0 -0
  86. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/manager.py +0 -0
  87. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/nl_query.py +0 -0
  88. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/parser_loader.py +0 -0
  89. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/models.py +0 -0
  90. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/service.py +0 -0
  91. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/llm_proxy/__init__.py +0 -0
  92. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/llm_proxy/clients.py +0 -0
  93. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/llm_proxy/constants.py +0 -0
  94. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/logging_config.py +0 -0
  95. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/main.py +0 -0
  96. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/posthog_telemetry.py +0 -0
  97. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/__init__.py +0 -0
  98. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/__init__.py +0 -0
  99. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/export.j2 +0 -0
  100. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  101. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  102. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  103. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  104. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/plan.j2 +0 -0
  105. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/research.j2 +0 -0
  106. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  107. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  108. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  109. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/__init__.py +0 -0
  110. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  111. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  112. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  113. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  114. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  115. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  116. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/history/__init__.py +0 -0
  117. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  118. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/history/summarization.j2 +0 -0
  119. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/loader.py +0 -0
  120. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/tools/web_search.j2 +0 -0
  121. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/py.typed +0 -0
  122. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/__init__.py +0 -0
  123. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/codebase.py +0 -0
  124. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/exceptions.py +0 -0
  125. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/models.py +0 -0
  126. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/services.py +0 -0
  127. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/sentry_telemetry.py +0 -0
  128. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/shotgun_web/__init__.py +0 -0
  129. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/shotgun_web/client.py +0 -0
  130. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/shotgun_web/constants.py +0 -0
  131. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/shotgun_web/models.py +0 -0
  132. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/telemetry.py +0 -0
  133. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/__init__.py +0 -0
  134. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/app.py +0 -0
  135. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/commands/__init__.py +0 -0
  136. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/components/prompt_input.py +0 -0
  137. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/components/spinner.py +0 -0
  138. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/components/splash.py +0 -0
  139. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/components/vertical_tail.py +0 -0
  140. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/filtered_codebase_service.py +0 -0
  141. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat.tcss +0 -0
  142. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  143. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
  144. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  145. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
  146. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/directory_setup.py +0 -0
  147. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/feedback.py +0 -0
  148. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/model_picker.py +0 -0
  149. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/provider_config.py +0 -0
  150. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/shotgun_auth.py +0 -0
  151. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/splash.py +0 -0
  152. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/welcome.py +0 -0
  153. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/styles.tcss +0 -0
  154. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/utils/__init__.py +0 -0
  155. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/utils/mode_progress.py +0 -0
  156. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/__init__.py +0 -0
  157. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/datetime_utils.py +0 -0
  158. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/env_utils.py +0 -0
  159. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/file_system_utils.py +0 -0
  160. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/source_detection.py +0 -0
  161. {shotgun_sh-0.2.6.dev5 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/update_checker.py +0 -0
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: shotgun-sh
3
+ Version: 0.2.7.dev2
4
+ Summary: AI-powered research, planning, and task management CLI tool
5
+ Project-URL: Homepage, https://shotgun.sh/
6
+ Project-URL: Repository, https://github.com/shotgun-sh/shotgun
7
+ Project-URL: Issues, https://github.com/shotgun-sh/shotgun-alpha/issues
8
+ Project-URL: Discord, https://discord.gg/5RmY6J2N7s
9
+ Author-email: "Proofs.io" <hello@proofs.io>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,ai,cli,llm,planning,productivity,pydantic-ai,research,task-management
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: anthropic>=0.39.0
25
+ Requires-Dist: genai-prices>=0.0.27
26
+ Requires-Dist: httpx>=0.27.0
27
+ Requires-Dist: jinja2>=3.1.0
28
+ Requires-Dist: kuzu>=0.7.0
29
+ Requires-Dist: logfire[pydantic-ai]>=2.0.0
30
+ Requires-Dist: openai>=1.0.0
31
+ Requires-Dist: packaging>=23.0
32
+ Requires-Dist: posthog>=3.0.0
33
+ Requires-Dist: pydantic-ai>=0.0.14
34
+ Requires-Dist: rich>=13.0.0
35
+ Requires-Dist: sentencepiece>=0.2.0
36
+ Requires-Dist: sentry-sdk[pure-eval]>=2.0.0
37
+ Requires-Dist: tenacity>=8.0.0
38
+ Requires-Dist: textual-dev>=1.7.0
39
+ Requires-Dist: textual>=6.1.0
40
+ Requires-Dist: tiktoken>=0.7.0
41
+ Requires-Dist: tree-sitter-go>=0.23.0
42
+ Requires-Dist: tree-sitter-javascript>=0.23.0
43
+ Requires-Dist: tree-sitter-python>=0.23.0
44
+ Requires-Dist: tree-sitter-rust>=0.23.0
45
+ Requires-Dist: tree-sitter-typescript>=0.23.0
46
+ Requires-Dist: tree-sitter>=0.21.0
47
+ Requires-Dist: typer>=0.12.0
48
+ Requires-Dist: watchdog>=4.0.0
49
+ Provides-Extra: dev
50
+ Requires-Dist: commitizen>=3.13.0; extra == 'dev'
51
+ Requires-Dist: lefthook>=1.12.0; extra == 'dev'
52
+ Requires-Dist: mypy>=1.11.0; extra == 'dev'
53
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
54
+ Description-Content-Type: text/markdown
55
+
56
+ # Shotgun
57
+
58
+ **Spec-Driven Development for AI Code Generation**
59
+
60
+ Shotgun is a CLI tool that turns work with AI code-gen tools from "I want to build X" into: **research → specs → plans → tasks → implementation**. It reads your entire codebase, coordinates AI agents to do the heavy lifting, and exports clean artifacts in the agents.md format so your code-gen tools actually know what they're building.
61
+
62
+ 🌐 **Learn more at [shotgun.sh](https://shotgun.sh/)**
63
+
64
+ ## Features
65
+
66
+ ### 📊 Complete Codebase Understanding
67
+
68
+ Before writing a single line, Shotgun reads all of it. Your patterns. Your dependencies. Your technical debt. Whether you're adding features, onboarding devs, planning migrations, or refactoring - Shotgun knows what you're working with.
69
+
70
+ ### 🔄 Five Modes. One Journey. Zero Gaps.
71
+
72
+ **Research** (what exists) → **Specify** (what to build) → **Plan** (how to build) → **Tasks** (break it down) → **Export** (to any tool)
73
+
74
+ Not another chatbot. A complete workflow where each mode feeds the next.
75
+
76
+ ### ➡️ Export to agents.md
77
+
78
+ Outputs plug into many code-generation tools including Codex, Cursor, Warp, Devin, opencode, Jules, and more.
79
+
80
+ ### 📝 Specs That Don't Die in Slack
81
+
82
+ Every research finding, every architectural decision, every "here's why we didn't use that library" - captured as markdown in your repo. Version controlled. Searchable.
83
+
84
+ ## Installation
85
+
86
+ ### Using pipx (Recommended)
87
+
88
+ ```bash
89
+ pipx install shotgun-sh
90
+ ```
91
+
92
+ **Why pipx?** It installs Shotgun in an isolated environment, preventing dependency conflicts with your other Python projects.
93
+
94
+ ### Using pip
95
+
96
+ ```bash
97
+ pip install shotgun-sh
98
+ ```
99
+
100
+ ## Quick Start
101
+
102
+ ```bash
103
+ # Research your codebase or a topic
104
+ shotgun research "What is our authentication flow?"
105
+
106
+ # Generate specifications
107
+ shotgun spec "Add OAuth2 authentication"
108
+
109
+ # Create an implementation plan
110
+ shotgun plan "Build user dashboard"
111
+
112
+ # Break down into tasks
113
+ shotgun tasks "Implement payment system"
114
+
115
+ # Export to agents.md format for your code-gen tools
116
+ shotgun export
117
+ ```
118
+
119
+ ## Support
120
+
121
+ Have questions? Join our community on **[Discord](https://discord.gg/5RmY6J2N7s)**
122
+
123
+ ---
124
+
125
+ **License:** MIT
126
+ **Python:** 3.11+
@@ -0,0 +1,71 @@
1
+ # Shotgun
2
+
3
+ **Spec-Driven Development for AI Code Generation**
4
+
5
+ Shotgun is a CLI tool that turns work with AI code-gen tools from "I want to build X" into: **research → specs → plans → tasks → implementation**. It reads your entire codebase, coordinates AI agents to do the heavy lifting, and exports clean artifacts in the agents.md format so your code-gen tools actually know what they're building.
6
+
7
+ 🌐 **Learn more at [shotgun.sh](https://shotgun.sh/)**
8
+
9
+ ## Features
10
+
11
+ ### 📊 Complete Codebase Understanding
12
+
13
+ Before writing a single line, Shotgun reads all of it. Your patterns. Your dependencies. Your technical debt. Whether you're adding features, onboarding devs, planning migrations, or refactoring - Shotgun knows what you're working with.
14
+
15
+ ### 🔄 Five Modes. One Journey. Zero Gaps.
16
+
17
+ **Research** (what exists) → **Specify** (what to build) → **Plan** (how to build) → **Tasks** (break it down) → **Export** (to any tool)
18
+
19
+ Not another chatbot. A complete workflow where each mode feeds the next.
20
+
21
+ ### ➡️ Export to agents.md
22
+
23
+ Outputs plug into many code-generation tools including Codex, Cursor, Warp, Devin, opencode, Jules, and more.
24
+
25
+ ### 📝 Specs That Don't Die in Slack
26
+
27
+ Every research finding, every architectural decision, every "here's why we didn't use that library" - captured as markdown in your repo. Version controlled. Searchable.
28
+
29
+ ## Installation
30
+
31
+ ### Using pipx (Recommended)
32
+
33
+ ```bash
34
+ pipx install shotgun-sh
35
+ ```
36
+
37
+ **Why pipx?** It installs Shotgun in an isolated environment, preventing dependency conflicts with your other Python projects.
38
+
39
+ ### Using pip
40
+
41
+ ```bash
42
+ pip install shotgun-sh
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```bash
48
+ # Research your codebase or a topic
49
+ shotgun research "What is our authentication flow?"
50
+
51
+ # Generate specifications
52
+ shotgun spec "Add OAuth2 authentication"
53
+
54
+ # Create an implementation plan
55
+ shotgun plan "Build user dashboard"
56
+
57
+ # Break down into tasks
58
+ shotgun tasks "Implement payment system"
59
+
60
+ # Export to agents.md format for your code-gen tools
61
+ shotgun export
62
+ ```
63
+
64
+ ## Support
65
+
66
+ Have questions? Join our community on **[Discord](https://discord.gg/5RmY6J2N7s)**
67
+
68
+ ---
69
+
70
+ **License:** MIT
71
+ **Python:** 3.11+
@@ -1,8 +1,8 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.2.6.dev5"
3
+ version = "0.2.7.dev2"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
- readme = "README.md"
5
+ readme = "README_PYPI.md"
6
6
  license = { text = "MIT" }
7
7
  authors = [
8
8
  { name = "Proofs.io", email = "hello@proofs.io" }
@@ -46,6 +46,7 @@ dependencies = [
46
46
  "sentencepiece>=0.2.0",
47
47
  "packaging>=23.0",
48
48
  "genai-prices>=0.0.27",
49
+ "tenacity>=8.0.0",
49
50
  ]
50
51
 
51
52
  [project.urls]
@@ -76,6 +77,7 @@ packages = ["src/shotgun"]
76
77
  include = [
77
78
  "/src",
78
79
  "/README.md",
80
+ "/README_PYPI.md",
79
81
  "/LICENSE",
80
82
  "/pyproject.toml"
81
83
  ]
@@ -1,10 +1,21 @@
1
1
  """Agent manager for coordinating multiple AI agents with shared message history."""
2
2
 
3
+ import json
3
4
  import logging
4
5
  from collections.abc import AsyncIterable, Sequence
5
6
  from dataclasses import dataclass, field, is_dataclass, replace
7
+ from pathlib import Path
6
8
  from typing import TYPE_CHECKING, Any, cast
7
9
 
10
+ import logfire
11
+ from tenacity import (
12
+ before_sleep_log,
13
+ retry,
14
+ retry_if_exception,
15
+ stop_after_attempt,
16
+ wait_exponential,
17
+ )
18
+
8
19
  if TYPE_CHECKING:
9
20
  from shotgun.agents.conversation_history import ConversationState
10
21
 
@@ -52,6 +63,35 @@ from .tasks import create_tasks_agent
52
63
  logger = logging.getLogger(__name__)
53
64
 
54
65
 
66
+ def _is_retryable_error(exception: BaseException) -> bool:
67
+ """Check if exception should trigger a retry.
68
+
69
+ Args:
70
+ exception: The exception to check.
71
+
72
+ Returns:
73
+ True if the exception is a transient error that should be retried.
74
+ """
75
+ # ValueError for truncated/incomplete JSON
76
+ if isinstance(exception, ValueError):
77
+ error_str = str(exception)
78
+ return "EOF while parsing" in error_str or (
79
+ "JSON" in error_str and "parsing" in error_str
80
+ )
81
+
82
+ # API errors (overload, rate limits)
83
+ exception_name = type(exception).__name__
84
+ if "APIStatusError" in exception_name:
85
+ error_str = str(exception)
86
+ return "overload" in error_str.lower() or "rate" in error_str.lower()
87
+
88
+ # Network errors
89
+ if "ConnectionError" in exception_name or "TimeoutError" in exception_name:
90
+ return True
91
+
92
+ return False
93
+
94
+
55
95
  class MessageHistoryUpdated(Message):
56
96
  """Event posted when the message history is updated."""
57
97
 
@@ -265,6 +305,49 @@ class AgentManager(Widget):
265
305
  f"Invalid agent type: {agent_type}. Must be one of: {', '.join(e.value for e in AgentType)}"
266
306
  ) from None
267
307
 
308
+ @retry(
309
+ stop=stop_after_attempt(3),
310
+ wait=wait_exponential(multiplier=1, min=1, max=8),
311
+ retry=retry_if_exception(_is_retryable_error),
312
+ before_sleep=before_sleep_log(logger, logging.WARNING),
313
+ reraise=True,
314
+ )
315
+ async def _run_agent_with_retry(
316
+ self,
317
+ agent: Agent[AgentDeps, AgentResponse],
318
+ prompt: str | None,
319
+ deps: AgentDeps,
320
+ usage_limits: UsageLimits | None,
321
+ message_history: list[ModelMessage],
322
+ event_stream_handler: Any,
323
+ **kwargs: Any,
324
+ ) -> AgentRunResult[AgentResponse]:
325
+ """Run agent with automatic retry on transient errors.
326
+
327
+ Args:
328
+ agent: The agent to run.
329
+ prompt: Optional prompt to send to the agent.
330
+ deps: Agent dependencies.
331
+ usage_limits: Optional usage limits.
332
+ message_history: Message history to provide to agent.
333
+ event_stream_handler: Event handler for streaming.
334
+ **kwargs: Additional keyword arguments.
335
+
336
+ Returns:
337
+ The agent run result.
338
+
339
+ Raises:
340
+ Various exceptions if all retries fail.
341
+ """
342
+ return await agent.run(
343
+ prompt,
344
+ deps=deps,
345
+ usage_limits=usage_limits,
346
+ message_history=message_history,
347
+ event_stream_handler=event_stream_handler,
348
+ **kwargs,
349
+ )
350
+
268
351
  async def run(
269
352
  self,
270
353
  prompt: str | None = None,
@@ -391,8 +474,9 @@ class AgentManager(Widget):
391
474
  )
392
475
 
393
476
  try:
394
- result: AgentRunResult[AgentResponse] = await self.current_agent.run(
395
- prompt,
477
+ result: AgentRunResult[AgentResponse] = await self._run_agent_with_retry(
478
+ agent=self.current_agent,
479
+ prompt=prompt,
396
480
  deps=deps,
397
481
  usage_limits=usage_limits,
398
482
  message_history=message_history,
@@ -401,18 +485,93 @@ class AgentManager(Widget):
401
485
  else None,
402
486
  **kwargs,
403
487
  )
488
+ except ValueError as e:
489
+ # Handle truncated/incomplete JSON in tool calls specifically
490
+ error_str = str(e)
491
+ if "EOF while parsing" in error_str or (
492
+ "JSON" in error_str and "parsing" in error_str
493
+ ):
494
+ logger.error(
495
+ "Tool call with truncated/incomplete JSON arguments detected",
496
+ extra={
497
+ "agent_mode": self._current_agent_type.value,
498
+ "model_name": model_name,
499
+ "error": error_str,
500
+ },
501
+ )
502
+ logfire.error(
503
+ "Tool call with truncated JSON arguments",
504
+ agent_mode=self._current_agent_type.value,
505
+ model_name=model_name,
506
+ error=error_str,
507
+ )
508
+ # Add helpful hint message for the user
509
+ self.ui_message_history.append(
510
+ HintMessage(
511
+ message="⚠️ The agent attempted an operation with arguments that were too large (truncated JSON). "
512
+ "Try breaking your request into smaller steps or more focused contracts."
513
+ )
514
+ )
515
+ self._post_messages_updated()
516
+ # Re-raise to maintain error visibility
517
+ raise
518
+ except Exception as e:
519
+ # Log the error with full stack trace to shotgun.log and Logfire
520
+ logger.exception(
521
+ "Agent execution failed",
522
+ extra={
523
+ "agent_mode": self._current_agent_type.value,
524
+ "model_name": model_name,
525
+ "error_type": type(e).__name__,
526
+ },
527
+ )
528
+ logfire.exception(
529
+ "Agent execution failed",
530
+ agent_mode=self._current_agent_type.value,
531
+ model_name=model_name,
532
+ error_type=type(e).__name__,
533
+ )
534
+ # Re-raise to let TUI handle user messaging
535
+ raise
404
536
  finally:
405
537
  self._stream_state = None
406
538
 
407
539
  # Agent ALWAYS returns AgentResponse with structured output
408
540
  agent_response = result.output
409
- logger.debug("Agent returned structured AgentResponse")
541
+ logger.debug(
542
+ "Agent returned structured AgentResponse",
543
+ extra={
544
+ "has_response": agent_response.response is not None,
545
+ "response_length": len(agent_response.response)
546
+ if agent_response.response
547
+ else 0,
548
+ "response_preview": agent_response.response[:100] + "..."
549
+ if agent_response.response and len(agent_response.response) > 100
550
+ else agent_response.response or "(empty)",
551
+ "has_clarifying_questions": bool(agent_response.clarifying_questions),
552
+ "num_clarifying_questions": len(agent_response.clarifying_questions)
553
+ if agent_response.clarifying_questions
554
+ else 0,
555
+ },
556
+ )
410
557
 
411
558
  # Always add the agent's response messages to maintain conversation history
412
559
  self.ui_message_history = original_messages + cast(
413
560
  list[ModelRequest | ModelResponse | HintMessage], result.new_messages()
414
561
  )
415
562
 
563
+ # Get file operations early so we can use them for contextual messages
564
+ file_operations = deps.file_tracker.operations.copy()
565
+ self.recently_change_files = file_operations
566
+
567
+ logger.debug(
568
+ "File operations tracked",
569
+ extra={
570
+ "num_file_operations": len(file_operations),
571
+ "operation_files": [Path(op.file_path).name for op in file_operations],
572
+ },
573
+ )
574
+
416
575
  # Check if there are clarifying questions
417
576
  if agent_response.clarifying_questions:
418
577
  logger.info(
@@ -459,27 +618,93 @@ class AgentManager(Widget):
459
618
  response_text=agent_response.response,
460
619
  )
461
620
  )
621
+
622
+ # Post UI update with hint messages and file operations
623
+ logger.debug(
624
+ "Posting UI update for Q&A mode with hint messages and file operations"
625
+ )
626
+ self._post_messages_updated(file_operations)
462
627
  else:
463
- # No clarifying questions - just show the response if present
628
+ # No clarifying questions - show the response or a default success message
464
629
  if agent_response.response and agent_response.response.strip():
630
+ logger.debug(
631
+ "Adding agent response as hint",
632
+ extra={
633
+ "response_preview": agent_response.response[:100] + "..."
634
+ if len(agent_response.response) > 100
635
+ else agent_response.response,
636
+ "has_file_operations": len(file_operations) > 0,
637
+ },
638
+ )
465
639
  self.ui_message_history.append(
466
640
  HintMessage(message=agent_response.response)
467
641
  )
642
+ else:
643
+ # Fallback: response is empty or whitespace
644
+ logger.debug(
645
+ "Agent response was empty, using fallback completion message",
646
+ extra={"has_file_operations": len(file_operations) > 0},
647
+ )
648
+ # Show contextual message based on whether files were modified
649
+ if file_operations:
650
+ self.ui_message_history.append(
651
+ HintMessage(
652
+ message="✅ Task completed - files have been modified"
653
+ )
654
+ )
655
+ else:
656
+ self.ui_message_history.append(
657
+ HintMessage(message="✅ Task completed")
658
+ )
659
+
660
+ # Post UI update immediately so user sees the response without delay
661
+ logger.debug(
662
+ "Posting immediate UI update with hint message and file operations"
663
+ )
664
+ self._post_messages_updated(file_operations)
468
665
 
469
666
  # Apply compaction to persistent message history to prevent cascading growth
470
667
  all_messages = result.all_messages()
471
- self.message_history = await apply_persistent_compaction(all_messages, deps)
472
- usage = result.usage()
473
- deps.usage_manager.add_usage(
474
- usage, model_name=deps.llm_model.name, provider=deps.llm_model.provider
475
- )
668
+ try:
669
+ logger.debug(
670
+ "Starting message history compaction",
671
+ extra={"message_count": len(all_messages)},
672
+ )
673
+ self.message_history = await apply_persistent_compaction(all_messages, deps)
674
+ logger.debug(
675
+ "Completed message history compaction",
676
+ extra={
677
+ "original_count": len(all_messages),
678
+ "compacted_count": len(self.message_history),
679
+ },
680
+ )
681
+ except Exception as e:
682
+ # If compaction fails, log full error with stack trace and use uncompacted messages
683
+ logger.error(
684
+ "Failed to compact message history - using uncompacted messages",
685
+ exc_info=True,
686
+ extra={
687
+ "error": str(e),
688
+ "message_count": len(all_messages),
689
+ "agent_mode": self._current_agent_type.value,
690
+ },
691
+ )
692
+ # Fallback: use uncompacted messages to prevent data loss
693
+ self.message_history = all_messages
476
694
 
477
- # Log file operations summary if any files were modified
478
- file_operations = deps.file_tracker.operations.copy()
479
- self.recently_change_files = file_operations
695
+ usage = result.usage()
696
+ if hasattr(deps, "llm_model") and deps.llm_model is not None:
697
+ deps.usage_manager.add_usage(
698
+ usage, model_name=deps.llm_model.name, provider=deps.llm_model.provider
699
+ )
700
+ else:
701
+ logger.warning(
702
+ "llm_model is None, skipping usage tracking",
703
+ extra={"agent_mode": self._current_agent_type.value},
704
+ )
480
705
 
481
- # Post message history update (hints are now added synchronously above)
482
- self._post_messages_updated(file_operations)
706
+ # UI updates are now posted immediately in each branch (Q&A or non-Q&A)
707
+ # before compaction, so no duplicate posting needed here
483
708
 
484
709
  return result
485
710
 
@@ -554,6 +779,39 @@ class AgentManager(Widget):
554
779
  # Detect source from call stack
555
780
  source = detect_source()
556
781
 
782
+ # Log if tool call has incomplete args (for debugging truncated JSON)
783
+ if isinstance(event.part.args, str):
784
+ try:
785
+ json.loads(event.part.args)
786
+ except (json.JSONDecodeError, ValueError):
787
+ args_preview = (
788
+ event.part.args[:100] + "..."
789
+ if len(event.part.args) > 100
790
+ else event.part.args
791
+ )
792
+ logger.warning(
793
+ "FunctionToolCallEvent received with incomplete JSON args",
794
+ extra={
795
+ "tool_name": event.part.tool_name,
796
+ "tool_call_id": event.part.tool_call_id,
797
+ "args_preview": args_preview,
798
+ "args_length": len(event.part.args)
799
+ if event.part.args
800
+ else 0,
801
+ "agent_mode": self._current_agent_type.value,
802
+ },
803
+ )
804
+ logfire.warn(
805
+ "FunctionToolCallEvent received with incomplete JSON args",
806
+ tool_name=event.part.tool_name,
807
+ tool_call_id=event.part.tool_call_id,
808
+ args_preview=args_preview,
809
+ args_length=len(event.part.args)
810
+ if event.part.args
811
+ else 0,
812
+ agent_mode=self._current_agent_type.value,
813
+ )
814
+
557
815
  track_event(
558
816
  "tool_called",
559
817
  {
@@ -384,23 +384,48 @@ def get_agent_existing_files(agent_mode: AgentType | None = None) -> list[str]:
384
384
  relative_path = file_path.relative_to(base_path)
385
385
  existing_files.append(str(relative_path))
386
386
  else:
387
- # For other agents, check both .md file and directory with same name
388
- allowed_file = AGENT_DIRECTORIES[agent_mode]
389
-
390
- # Check for the .md file
391
- md_file_path = base_path / allowed_file
392
- if md_file_path.exists():
393
- existing_files.append(allowed_file)
394
-
395
- # Check for directory with same base name (e.g., research/ for research.md)
396
- base_name = allowed_file.replace(".md", "")
397
- dir_path = base_path / base_name
398
- if dir_path.exists() and dir_path.is_dir():
399
- # List all files in the directory
400
- for file_path in dir_path.rglob("*"):
401
- if file_path.is_file():
402
- relative_path = file_path.relative_to(base_path)
403
- existing_files.append(str(relative_path))
387
+ # For other agents, check files/directories they have access to
388
+ allowed_paths_raw = AGENT_DIRECTORIES[agent_mode]
389
+
390
+ # Convert single Path/string to list of Paths for uniform handling
391
+ if isinstance(allowed_paths_raw, str):
392
+ # Special case: "*" means export agent (shouldn't reach here but handle it)
393
+ allowed_paths = (
394
+ [Path(allowed_paths_raw)] if allowed_paths_raw != "*" else []
395
+ )
396
+ elif isinstance(allowed_paths_raw, Path):
397
+ allowed_paths = [allowed_paths_raw]
398
+ else:
399
+ # Already a list
400
+ allowed_paths = allowed_paths_raw
401
+
402
+ # Check each allowed path
403
+ for allowed_path in allowed_paths:
404
+ allowed_str = str(allowed_path)
405
+
406
+ # Check if it's a directory (no .md suffix)
407
+ if not allowed_path.suffix or not allowed_str.endswith(".md"):
408
+ # It's a directory - list all files within it
409
+ dir_path = base_path / allowed_str
410
+ if dir_path.exists() and dir_path.is_dir():
411
+ for file_path in dir_path.rglob("*"):
412
+ if file_path.is_file():
413
+ relative_path = file_path.relative_to(base_path)
414
+ existing_files.append(str(relative_path))
415
+ else:
416
+ # It's a file - check if it exists
417
+ file_path = base_path / allowed_str
418
+ if file_path.exists():
419
+ existing_files.append(allowed_str)
420
+
421
+ # Also check for associated directory (e.g., research/ for research.md)
422
+ base_name = allowed_str.replace(".md", "")
423
+ dir_path = base_path / base_name
424
+ if dir_path.exists() and dir_path.is_dir():
425
+ for file_path in dir_path.rglob("*"):
426
+ if file_path.is_file():
427
+ relative_path = file_path.relative_to(base_path)
428
+ existing_files.append(str(relative_path))
404
429
 
405
430
  return existing_files
406
431