shotgun-sh 0.5.2.dev2__tar.gz → 0.5.2.dev3__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 (283) hide show
  1. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/.gitignore +1 -1
  2. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/PKG-INFO +1 -1
  3. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/pyproject.toml +1 -1
  4. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/common.py +4 -0
  5. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/__init__.py +3 -0
  6. shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/__init__.py +33 -0
  7. shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/insert_section.py +195 -0
  8. shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/models.py +33 -0
  9. shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/replace_section.py +186 -0
  10. shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/utils.py +157 -0
  11. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/LICENSE +0 -0
  12. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/README.md +0 -0
  13. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/hatch_build.py +0 -0
  14. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/__init__.py +0 -0
  15. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/__init__.py +0 -0
  16. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/agent_manager.py +0 -0
  17. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/README.md +0 -0
  18. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/__init__.py +0 -0
  19. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/constants.py +0 -0
  20. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/manager.py +0 -0
  21. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/models.py +0 -0
  22. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/provider.py +0 -0
  23. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/streaming_test.py +0 -0
  24. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/__init__.py +0 -0
  25. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/analyzer.py +0 -0
  26. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/constants.py +0 -0
  27. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/formatter.py +0 -0
  28. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/models.py +0 -0
  29. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/__init__.py +0 -0
  30. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/filters.py +0 -0
  31. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/__init__.py +0 -0
  32. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/chunking.py +0 -0
  33. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/compaction.py +0 -0
  34. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/constants.py +0 -0
  35. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/context_extraction.py +0 -0
  36. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/file_content_deduplication.py +0 -0
  37. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/history_building.py +0 -0
  38. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/history_processors.py +0 -0
  39. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/message_utils.py +0 -0
  40. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/__init__.py +0 -0
  41. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/anthropic.py +0 -0
  42. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/base.py +0 -0
  43. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/openai.py +0 -0
  44. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/sentencepiece_counter.py +0 -0
  45. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/tokenizer_cache.py +0 -0
  46. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/utils.py +0 -0
  47. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_estimation.py +0 -0
  48. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/manager.py +0 -0
  49. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/models.py +0 -0
  50. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/error/__init__.py +0 -0
  51. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/error/models.py +0 -0
  52. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/export.py +0 -0
  53. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/llm.py +0 -0
  54. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/messages.py +0 -0
  55. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/models.py +0 -0
  56. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/plan.py +0 -0
  57. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/research.py +0 -0
  58. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/__init__.py +0 -0
  59. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/models.py +0 -0
  60. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/router.py +0 -0
  61. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/tools/__init__.py +0 -0
  62. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/tools/delegation_tools.py +0 -0
  63. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/tools/plan_tools.py +0 -0
  64. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/runner.py +0 -0
  65. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/specify.py +0 -0
  66. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tasks.py +0 -0
  67. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  68. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  69. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  70. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  71. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/models.py +0 -0
  72. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  73. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  74. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/file_management.py +0 -0
  75. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/registry.py +0 -0
  76. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  77. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  78. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  79. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  80. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  81. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/usage_manager.py +0 -0
  82. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/api_endpoints.py +0 -0
  83. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/build_constants.py +0 -0
  84. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/__init__.py +0 -0
  85. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/clear.py +0 -0
  86. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/codebase/__init__.py +0 -0
  87. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/codebase/commands.py +0 -0
  88. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/codebase/models.py +0 -0
  89. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/compact.py +0 -0
  90. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/config.py +0 -0
  91. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/context.py +0 -0
  92. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/error_handler.py +0 -0
  93. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/export.py +0 -0
  94. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/feedback.py +0 -0
  95. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/models.py +0 -0
  96. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/plan.py +0 -0
  97. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/research.py +0 -0
  98. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/__init__.py +0 -0
  99. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/backup.py +0 -0
  100. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/commands.py +0 -0
  101. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/models.py +0 -0
  102. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/pull_service.py +0 -0
  103. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/specify.py +0 -0
  104. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/tasks.py +0 -0
  105. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/update.py +0 -0
  106. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/utils.py +0 -0
  107. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/__init__.py +0 -0
  108. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/__init__.py +0 -0
  109. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/benchmark_runner.py +0 -0
  110. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/exporters.py +0 -0
  111. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/formatters/__init__.py +0 -0
  112. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/formatters/base.py +0 -0
  113. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/formatters/json_formatter.py +0 -0
  114. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/formatters/markdown.py +0 -0
  115. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/models.py +0 -0
  116. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/__init__.py +0 -0
  117. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/call_resolution.py +0 -0
  118. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/change_detector.py +0 -0
  119. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  120. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/cypher_models.py +0 -0
  121. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/errors.py +0 -0
  122. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/__init__.py +0 -0
  123. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/base.py +0 -0
  124. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/factory.py +0 -0
  125. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/go/__init__.py +0 -0
  126. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/go/extractor.py +0 -0
  127. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/javascript/__init__.py +0 -0
  128. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/javascript/extractor.py +0 -0
  129. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/protocol.py +0 -0
  130. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/python/__init__.py +0 -0
  131. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/python/extractor.py +0 -0
  132. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/rust/__init__.py +0 -0
  133. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/rust/extractor.py +0 -0
  134. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/types.py +0 -0
  135. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/typescript/__init__.py +0 -0
  136. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/typescript/extractor.py +0 -0
  137. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/gitignore.py +0 -0
  138. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/ingestor.py +0 -0
  139. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/kuzu_compat.py +0 -0
  140. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/language_config.py +0 -0
  141. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/manager.py +0 -0
  142. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/metrics_collector.py +0 -0
  143. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/metrics_types.py +0 -0
  144. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/nl_query.py +0 -0
  145. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/parallel_executor.py +0 -0
  146. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/parser_loader.py +0 -0
  147. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/work_distributor.py +0 -0
  148. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/worker.py +0 -0
  149. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/indexing_state.py +0 -0
  150. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/models.py +0 -0
  151. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/service.py +0 -0
  152. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/exceptions.py +0 -0
  153. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/__init__.py +0 -0
  154. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/client.py +0 -0
  155. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/clients.py +0 -0
  156. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/constants.py +0 -0
  157. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/models.py +0 -0
  158. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/logging_config.py +0 -0
  159. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/main.py +0 -0
  160. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/posthog_telemetry.py +0 -0
  161. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/__init__.py +0 -0
  162. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/__init__.py +0 -0
  163. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/export.j2 +0 -0
  164. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  165. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  166. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  167. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  168. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/router_delegation_mode.j2 +0 -0
  169. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/plan.j2 +0 -0
  170. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/research.j2 +0 -0
  171. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/router.j2 +0 -0
  172. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/specify.j2 +0 -0
  173. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  174. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  175. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  176. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/__init__.py +0 -0
  177. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  178. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  179. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  180. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  181. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  182. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  183. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/__init__.py +0 -0
  184. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/chunk_summarization.j2 +0 -0
  185. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/combine_summaries.j2 +0 -0
  186. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  187. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/summarization.j2 +0 -0
  188. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/loader.py +0 -0
  189. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/tools/web_search.j2 +0 -0
  190. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/py.typed +0 -0
  191. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/__init__.py +0 -0
  192. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/codebase.py +0 -0
  193. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/exceptions.py +0 -0
  194. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/models.py +0 -0
  195. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/services.py +0 -0
  196. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sentry_telemetry.py +0 -0
  197. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/settings.py +0 -0
  198. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/__init__.py +0 -0
  199. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/client.py +0 -0
  200. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/constants.py +0 -0
  201. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/exceptions.py +0 -0
  202. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/models.py +0 -0
  203. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/__init__.py +0 -0
  204. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/file_scanner.py +0 -0
  205. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/hasher.py +0 -0
  206. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/models.py +0 -0
  207. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/upload_pipeline.py +0 -0
  208. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/utils.py +0 -0
  209. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/specs_client.py +0 -0
  210. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/supabase_client.py +0 -0
  211. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/telemetry.py +0 -0
  212. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/__init__.py +0 -0
  213. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/app.py +0 -0
  214. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/commands/__init__.py +0 -0
  215. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/context_indicator.py +0 -0
  216. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/mode_indicator.py +0 -0
  217. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/prompt_input.py +0 -0
  218. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/spinner.py +0 -0
  219. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/splash.py +0 -0
  220. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/status_bar.py +0 -0
  221. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/vertical_tail.py +0 -0
  222. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/containers.py +0 -0
  223. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/dependencies.py +0 -0
  224. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/filtered_codebase_service.py +0 -0
  225. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/layout.py +0 -0
  226. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/protocols.py +0 -0
  227. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/__init__.py +0 -0
  228. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/chat.tcss +0 -0
  229. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/chat_screen.py +0 -0
  230. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/codebase_index_prompt_screen.py +0 -0
  231. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/codebase_index_selection.py +0 -0
  232. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/help_text.py +0 -0
  233. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/prompt_history.py +0 -0
  234. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat.tcss +0 -0
  235. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  236. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
  237. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  238. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/__init__.py +0 -0
  239. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/agent_response.py +0 -0
  240. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/chat_history.py +0 -0
  241. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/formatters.py +0 -0
  242. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/partial_response.py +0 -0
  243. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/user_question.py +0 -0
  244. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/messages.py +0 -0
  245. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/confirmation_dialog.py +0 -0
  246. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/database_locked_dialog.py +0 -0
  247. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/database_timeout_dialog.py +0 -0
  248. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/directory_setup.py +0 -0
  249. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/feedback.py +0 -0
  250. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/github_issue.py +0 -0
  251. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/kuzu_error_dialog.py +0 -0
  252. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/model_picker.py +0 -0
  253. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/pipx_migration.py +0 -0
  254. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/provider_config.py +0 -0
  255. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/__init__.py +0 -0
  256. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/create_spec_dialog.py +0 -0
  257. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/models.py +0 -0
  258. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/share_specs_dialog.py +0 -0
  259. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/upload_progress_screen.py +0 -0
  260. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shotgun_auth.py +0 -0
  261. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/spec_pull.py +0 -0
  262. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/splash.py +0 -0
  263. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/welcome.py +0 -0
  264. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/services/__init__.py +0 -0
  265. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/services/conversation_service.py +0 -0
  266. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/state/__init__.py +0 -0
  267. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/state/processing_state.py +0 -0
  268. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/styles.tcss +0 -0
  269. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/utils/__init__.py +0 -0
  270. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/utils/mode_progress.py +0 -0
  271. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/__init__.py +0 -0
  272. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/approval_widget.py +0 -0
  273. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/cascade_confirmation_widget.py +0 -0
  274. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/plan_panel.py +0 -0
  275. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/step_checkpoint_widget.py +0 -0
  276. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/widget_coordinator.py +0 -0
  277. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/__init__.py +0 -0
  278. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/datetime_utils.py +0 -0
  279. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/env_utils.py +0 -0
  280. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/file_system_utils.py +0 -0
  281. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/marketing.py +0 -0
  282. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/source_detection.py +0 -0
  283. {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/update_checker.py +0 -0
@@ -5,7 +5,7 @@ tmp/
5
5
  playground/
6
6
 
7
7
  # Output directory
8
- .shotgun/
8
+ .shotgun*
9
9
 
10
10
  # Generated build files
11
11
  src/shotgun/build_constants.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgun-sh
3
- Version: 0.5.2.dev2
3
+ Version: 0.5.2.dev3
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.5.2.dev2"
3
+ version = "0.5.2.dev3"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -38,8 +38,10 @@ from .tools import (
38
38
  codebase_shell,
39
39
  directory_lister,
40
40
  file_read,
41
+ insert_markdown_section,
41
42
  query_graph,
42
43
  read_file,
44
+ replace_markdown_section,
43
45
  retrieve_code,
44
46
  write_file,
45
47
  )
@@ -204,6 +206,8 @@ async def create_base_agent(
204
206
  agent.tool(write_file)
205
207
  agent.tool(append_file)
206
208
  agent.tool(read_file)
209
+ agent.tool(replace_markdown_section)
210
+ agent.tool(insert_markdown_section)
207
211
 
208
212
  # Register codebase understanding tools (conditional)
209
213
  if load_codebase_understanding_tools:
@@ -8,6 +8,7 @@ from .codebase import (
8
8
  retrieve_code,
9
9
  )
10
10
  from .file_management import append_file, read_file, write_file
11
+ from .markdown_tools import insert_markdown_section, replace_markdown_section
11
12
  from .web_search import (
12
13
  anthropic_web_search_tool,
13
14
  gemini_web_search_tool,
@@ -23,6 +24,8 @@ __all__ = [
23
24
  "read_file",
24
25
  "write_file",
25
26
  "append_file",
27
+ "replace_markdown_section",
28
+ "insert_markdown_section",
26
29
  # Codebase understanding tools
27
30
  "query_graph",
28
31
  "retrieve_code",
@@ -0,0 +1,33 @@
1
+ """Markdown manipulation tools for Pydantic AI agents."""
2
+
3
+ from .insert_section import insert_markdown_section
4
+ from .models import CloseMatch, HeadingList, HeadingMatch, MarkdownHeading
5
+ from .replace_section import replace_markdown_section
6
+ from .utils import (
7
+ detect_line_ending,
8
+ extract_headings,
9
+ find_close_matches,
10
+ find_matching_heading,
11
+ find_section_bounds,
12
+ get_heading_level,
13
+ normalize_section_content,
14
+ )
15
+
16
+ __all__ = [
17
+ # Tools
18
+ "replace_markdown_section",
19
+ "insert_markdown_section",
20
+ # Models
21
+ "MarkdownHeading",
22
+ "HeadingList",
23
+ "HeadingMatch",
24
+ "CloseMatch",
25
+ # Utilities
26
+ "get_heading_level",
27
+ "extract_headings",
28
+ "find_matching_heading",
29
+ "find_close_matches",
30
+ "find_section_bounds",
31
+ "detect_line_ending",
32
+ "normalize_section_content",
33
+ ]
@@ -0,0 +1,195 @@
1
+ """Tool for inserting content into markdown sections."""
2
+
3
+ import aiofiles
4
+ import aiofiles.os
5
+ from pydantic_ai import RunContext
6
+
7
+ from shotgun.agents.models import AgentDeps, FileOperationType
8
+ from shotgun.agents.tools.file_management import _validate_agent_scoped_path
9
+ from shotgun.agents.tools.registry import ToolCategory, register_tool
10
+ from shotgun.logging_config import get_logger
11
+
12
+ from .utils import (
13
+ detect_line_ending,
14
+ extract_headings,
15
+ find_close_matches,
16
+ find_matching_heading,
17
+ find_section_bounds,
18
+ normalize_section_content,
19
+ )
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ @register_tool(
25
+ category=ToolCategory.ARTIFACT_MANAGEMENT,
26
+ display_text="Inserting content",
27
+ key_arg="filename",
28
+ )
29
+ async def insert_markdown_section(
30
+ ctx: RunContext[AgentDeps],
31
+ filename: str,
32
+ after_heading: str,
33
+ content: str,
34
+ new_heading: str | None = None,
35
+ ) -> str:
36
+ """Insert content at the end of a Markdown section.
37
+
38
+ PREFER THIS TOOL over rewriting the entire file - it is faster, less costly,
39
+ and less error-prone. Use this to append content to an existing section.
40
+
41
+ Uses fuzzy matching on headings so minor typos are tolerated.
42
+ Inserts content just before the next heading at the same or higher level.
43
+
44
+ Args:
45
+ ctx: Run context with agent dependencies
46
+ filename: Path to the Markdown file (relative to .shotgun directory)
47
+ after_heading: The heading to insert after (e.g., '## Requirements'). Fuzzy matched.
48
+ content: The content to insert at the end of the section
49
+ new_heading: Optional heading for the inserted content (creates a subsection)
50
+
51
+ Returns:
52
+ Success message or error message
53
+ """
54
+ logger.debug("Inserting content into section '%s' in: %s", after_heading, filename)
55
+
56
+ try:
57
+ # Validate path with agent scoping
58
+ file_path = _validate_agent_scoped_path(filename, ctx.deps.agent_mode)
59
+
60
+ # Check if file exists
61
+ if not await aiofiles.os.path.exists(file_path):
62
+ return f"Error: File '{filename}' not found"
63
+
64
+ # Read file content (newline="" preserves original line endings)
65
+ async with aiofiles.open(file_path, encoding="utf-8", newline="") as f:
66
+ file_content = await f.read()
67
+
68
+ # Detect line ending style
69
+ line_ending = detect_line_ending(file_content)
70
+ lines = file_content.split("\n")
71
+
72
+ # Remove \r from lines if CRLF
73
+ if line_ending == "\r\n":
74
+ lines = [line.rstrip("\r") for line in lines]
75
+
76
+ # Extract headings
77
+ headings = extract_headings(file_content)
78
+
79
+ if not headings:
80
+ return f"Error: No headings found in '{filename}'. Cannot insert into files without headings."
81
+
82
+ # Find matching heading
83
+ match_result = find_matching_heading(headings, after_heading)
84
+
85
+ if match_result is None:
86
+ # No match found - provide helpful error with available headings
87
+ available = [h.text for h in headings]
88
+ close = find_close_matches(headings, after_heading)
89
+
90
+ if close and close[0].confidence >= 0.6:
91
+ # There are close matches but below threshold
92
+ close_display = ", ".join(
93
+ f"'{m.heading_text}' ({int(m.confidence * 100)}%)" for m in close
94
+ )
95
+ return (
96
+ f"No section matching '{after_heading}' found in {filename}. "
97
+ f"Did you mean: {close_display}"
98
+ )
99
+ else:
100
+ # List available headings
101
+ available_display = ", ".join(available[:5])
102
+ if len(available) > 5:
103
+ available_display += f" (+{len(available) - 5} more)"
104
+ return (
105
+ f"No section matching '{after_heading}' found in {filename}. "
106
+ f"Available headings: {available_display}"
107
+ )
108
+
109
+ matched = match_result.heading
110
+ confidence = match_result.confidence
111
+
112
+ # Check for ambiguous matches (multiple close matches)
113
+ if confidence < 1.0:
114
+ close = find_close_matches(
115
+ headings, after_heading, threshold=confidence - 0.1
116
+ )
117
+ if len(close) > 1 and close[1].confidence >= confidence - 0.05:
118
+ # Second match is very close to first - ambiguous
119
+ close_display = ", ".join(
120
+ f"'{m.heading_text}' ({int(m.confidence * 100)}%)"
121
+ for m in close[:3]
122
+ )
123
+ return (
124
+ f"Multiple sections closely match '{after_heading}' in {filename}: "
125
+ f"{close_display}. Please be more specific."
126
+ )
127
+
128
+ # Find section boundaries
129
+ _start_line, end_line = find_section_bounds(
130
+ lines, matched.line_number, matched.level
131
+ )
132
+
133
+ # Build insert content
134
+ normalized_content = normalize_section_content(content)
135
+ insert_content_lines = normalized_content.split("\n")
136
+ # Remove empty last line from split (since we added \n)
137
+ if insert_content_lines and insert_content_lines[-1] == "":
138
+ insert_content_lines.pop()
139
+
140
+ # Build the insert lines
141
+ insert_lines: list[str] = [""] # Blank line separator before new content
142
+
143
+ if new_heading:
144
+ insert_lines.append(new_heading)
145
+ insert_lines.append("") # Blank line after heading
146
+
147
+ insert_lines.extend(insert_content_lines)
148
+
149
+ # Add trailing blank line if not at EOF
150
+ if end_line < len(lines):
151
+ insert_lines.append("")
152
+
153
+ # Insert before section end (before next heading or EOF)
154
+ new_lines = lines[:end_line] + insert_lines + lines[end_line:]
155
+
156
+ # Join with detected line ending
157
+ new_content = line_ending.join(new_lines)
158
+
159
+ # Write file (newline="" preserves our chosen line endings)
160
+ async with aiofiles.open(file_path, "w", encoding="utf-8", newline="") as f:
161
+ await f.write(new_content)
162
+
163
+ # Track the file operation
164
+ ctx.deps.file_tracker.add_operation(file_path, FileOperationType.UPDATED)
165
+
166
+ logger.debug(
167
+ "Successfully inserted content into section '%s' in %s",
168
+ matched.text,
169
+ filename,
170
+ )
171
+
172
+ lines_added = len(insert_lines)
173
+ confidence_display = f"{int(confidence * 100)}%"
174
+
175
+ if new_heading:
176
+ return (
177
+ f"Successfully inserted '{new_heading}' into '{matched.text}' in {filename} "
178
+ f"(matched with {confidence_display} confidence, {lines_added} lines added)"
179
+ )
180
+ else:
181
+ return (
182
+ f"Successfully inserted content into '{matched.text}' in {filename} "
183
+ f"(matched with {confidence_display} confidence, {lines_added} lines added)"
184
+ )
185
+
186
+ except ValueError as e:
187
+ # Path validation errors
188
+ error_msg = f"Error inserting into '{filename}': {e}"
189
+ logger.error("Section insertion failed: %s", error_msg)
190
+ return error_msg
191
+
192
+ except Exception as e:
193
+ error_msg = f"Error inserting into '{filename}': {e}"
194
+ logger.error("Section insertion failed: %s", error_msg)
195
+ return error_msg
@@ -0,0 +1,33 @@
1
+ """Pydantic models for markdown tools."""
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class MarkdownHeading(BaseModel):
7
+ """Represents a heading found in a Markdown file."""
8
+
9
+ line_number: int
10
+ text: str
11
+ level: int
12
+
13
+ @property
14
+ def normalized_text(self) -> str:
15
+ """Return heading text without # prefix, stripped and lowercased."""
16
+ return self.text.lstrip("#").strip().lower()
17
+
18
+
19
+ HeadingList = list[MarkdownHeading]
20
+
21
+
22
+ class HeadingMatch(BaseModel):
23
+ """Result of a successful heading match."""
24
+
25
+ heading: MarkdownHeading
26
+ confidence: float
27
+
28
+
29
+ class CloseMatch(BaseModel):
30
+ """A close match result for error messages."""
31
+
32
+ heading_text: str
33
+ confidence: float
@@ -0,0 +1,186 @@
1
+ """Tool for replacing markdown sections."""
2
+
3
+ import aiofiles
4
+ import aiofiles.os
5
+ from pydantic_ai import RunContext
6
+
7
+ from shotgun.agents.models import AgentDeps, FileOperationType
8
+ from shotgun.agents.tools.file_management import _validate_agent_scoped_path
9
+ from shotgun.agents.tools.registry import ToolCategory, register_tool
10
+ from shotgun.logging_config import get_logger
11
+
12
+ from .utils import (
13
+ detect_line_ending,
14
+ extract_headings,
15
+ find_close_matches,
16
+ find_matching_heading,
17
+ find_section_bounds,
18
+ normalize_section_content,
19
+ )
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ @register_tool(
25
+ category=ToolCategory.ARTIFACT_MANAGEMENT,
26
+ display_text="Replacing section",
27
+ key_arg="filename",
28
+ )
29
+ async def replace_markdown_section(
30
+ ctx: RunContext[AgentDeps],
31
+ filename: str,
32
+ section_heading: str,
33
+ new_contents: str,
34
+ new_heading: str | None = None,
35
+ ) -> str:
36
+ """Replace an entire section in a Markdown file.
37
+
38
+ PREFER THIS TOOL over rewriting the entire file - it is faster, less costly,
39
+ and less error-prone.
40
+
41
+ Uses fuzzy matching on headings so minor typos are tolerated.
42
+ Replaces from the target heading down to (but not including) the next
43
+ heading at the same or higher level.
44
+
45
+ Args:
46
+ ctx: Run context with agent dependencies
47
+ filename: Path to the Markdown file (relative to .shotgun directory)
48
+ section_heading: The heading to find (e.g., '## Requirements'). Fuzzy matched.
49
+ new_contents: The new content for the section body (not including the heading)
50
+ new_heading: Optional new heading text to replace the old one
51
+
52
+ Returns:
53
+ Success message or error message
54
+ """
55
+ logger.debug("Replacing section '%s' in: %s", section_heading, filename)
56
+
57
+ try:
58
+ # Validate path with agent scoping
59
+ file_path = _validate_agent_scoped_path(filename, ctx.deps.agent_mode)
60
+
61
+ # Check if file exists
62
+ if not await aiofiles.os.path.exists(file_path):
63
+ return f"Error: File '{filename}' not found"
64
+
65
+ # Read file content (newline="" preserves original line endings)
66
+ async with aiofiles.open(file_path, encoding="utf-8", newline="") as f:
67
+ content = await f.read()
68
+
69
+ # Detect line ending style
70
+ line_ending = detect_line_ending(content)
71
+ lines = content.split("\n")
72
+
73
+ # Remove \r from lines if CRLF
74
+ if line_ending == "\r\n":
75
+ lines = [line.rstrip("\r") for line in lines]
76
+
77
+ # Extract headings
78
+ headings = extract_headings(content)
79
+
80
+ if not headings:
81
+ return f"Error: No headings found in '{filename}'. Cannot replace sections in files without headings."
82
+
83
+ # Find matching heading
84
+ match_result = find_matching_heading(headings, section_heading)
85
+
86
+ if match_result is None:
87
+ # No match found - provide helpful error with available headings
88
+ available = [h.text for h in headings]
89
+ close = find_close_matches(headings, section_heading)
90
+
91
+ if close and close[0].confidence >= 0.6:
92
+ # There are close matches but below threshold
93
+ close_display = ", ".join(
94
+ f"'{m.heading_text}' ({int(m.confidence * 100)}%)" for m in close
95
+ )
96
+ return (
97
+ f"No section matching '{section_heading}' found in {filename}. "
98
+ f"Did you mean: {close_display}"
99
+ )
100
+ else:
101
+ # List available headings
102
+ available_display = ", ".join(available[:5])
103
+ if len(available) > 5:
104
+ available_display += f" (+{len(available) - 5} more)"
105
+ return (
106
+ f"No section matching '{section_heading}' found in {filename}. "
107
+ f"Available headings: {available_display}"
108
+ )
109
+
110
+ matched = match_result.heading
111
+ confidence = match_result.confidence
112
+
113
+ # Check for ambiguous matches (multiple close matches)
114
+ if confidence < 1.0:
115
+ close = find_close_matches(
116
+ headings, section_heading, threshold=confidence - 0.1
117
+ )
118
+ if len(close) > 1 and close[1].confidence >= confidence - 0.05:
119
+ # Second match is very close to first - ambiguous
120
+ close_display = ", ".join(
121
+ f"'{m.heading_text}' ({int(m.confidence * 100)}%)"
122
+ for m in close[:3]
123
+ )
124
+ return (
125
+ f"Multiple sections closely match '{section_heading}' in {filename}: "
126
+ f"{close_display}. Please be more specific."
127
+ )
128
+
129
+ # Find section boundaries
130
+ start_line, end_line = find_section_bounds(
131
+ lines, matched.line_number, matched.level
132
+ )
133
+ old_section_lines = end_line - start_line
134
+
135
+ # Build new section
136
+ final_heading = new_heading if new_heading else matched.text
137
+ normalized_content = normalize_section_content(new_contents)
138
+
139
+ # Split new content into lines
140
+ new_content_lines = normalized_content.split("\n")
141
+ # Remove empty last line from split (since we added \n)
142
+ if new_content_lines and new_content_lines[-1] == "":
143
+ new_content_lines.pop()
144
+
145
+ # Build the new section: heading + blank line + content
146
+ new_section_lines = [final_heading, ""]
147
+ new_section_lines.extend(new_content_lines)
148
+
149
+ # Add trailing blank line if not at EOF
150
+ if end_line < len(lines):
151
+ new_section_lines.append("")
152
+
153
+ # Replace section
154
+ new_lines = lines[:start_line] + new_section_lines + lines[end_line:]
155
+
156
+ # Join with detected line ending
157
+ new_content = line_ending.join(new_lines)
158
+
159
+ # Write file (newline="" preserves our chosen line endings)
160
+ async with aiofiles.open(file_path, "w", encoding="utf-8", newline="") as f:
161
+ await f.write(new_content)
162
+
163
+ # Track the file operation
164
+ ctx.deps.file_tracker.add_operation(file_path, FileOperationType.UPDATED)
165
+
166
+ logger.debug("Successfully replaced section '%s' in %s", matched.text, filename)
167
+
168
+ new_section_line_count = len(new_section_lines)
169
+ confidence_display = f"{int(confidence * 100)}%"
170
+
171
+ return (
172
+ f"Successfully replaced section '{matched.text}' in {filename} "
173
+ f"(matched with {confidence_display} confidence, "
174
+ f"{old_section_lines} lines -> {new_section_line_count} lines)"
175
+ )
176
+
177
+ except ValueError as e:
178
+ # Path validation errors
179
+ error_msg = f"Error replacing section in '{filename}': {e}"
180
+ logger.error("Section replacement failed: %s", error_msg)
181
+ return error_msg
182
+
183
+ except Exception as e:
184
+ error_msg = f"Error replacing section in '{filename}': {e}"
185
+ logger.error("Section replacement failed: %s", error_msg)
186
+ return error_msg
@@ -0,0 +1,157 @@
1
+ """Utility functions for markdown parsing and manipulation."""
2
+
3
+ import re
4
+ from difflib import SequenceMatcher
5
+
6
+ from .models import CloseMatch, HeadingList, HeadingMatch, MarkdownHeading
7
+
8
+
9
+ def get_heading_level(line: str) -> int | None:
10
+ """Get the heading level (1-6) from a line, or None if not a heading.
11
+
12
+ Args:
13
+ line: A line of text to check
14
+
15
+ Returns:
16
+ The heading level (1-6) or None if not a heading
17
+ """
18
+ match = re.match(r"^(#{1,6})\s+", line)
19
+ return len(match.group(1)) if match else None
20
+
21
+
22
+ def extract_headings(content: str) -> HeadingList:
23
+ """Extract all headings from markdown content.
24
+
25
+ Args:
26
+ content: The markdown content to parse
27
+
28
+ Returns:
29
+ List of MarkdownHeading objects
30
+ """
31
+ headings: HeadingList = []
32
+ for i, line in enumerate(content.splitlines()):
33
+ level = get_heading_level(line)
34
+ if level is not None:
35
+ headings.append(MarkdownHeading(line_number=i, text=line, level=level))
36
+ return headings
37
+
38
+
39
+ def find_matching_heading(
40
+ headings: HeadingList,
41
+ target: str,
42
+ threshold: float = 0.8,
43
+ ) -> HeadingMatch | None:
44
+ """Find the best matching heading above the similarity threshold.
45
+
46
+ Args:
47
+ headings: List of MarkdownHeading objects
48
+ target: The target heading to match (e.g., "## Requirements")
49
+ threshold: Minimum similarity ratio (0.0-1.0)
50
+
51
+ Returns:
52
+ HeadingMatch with the matched heading and confidence, or None if no match
53
+ """
54
+ best_heading: MarkdownHeading | None = None
55
+ best_ratio = 0.0
56
+
57
+ # Normalize target: strip leading #s and whitespace, lowercase
58
+ norm_target = target.lstrip("#").strip().lower()
59
+
60
+ for heading in headings:
61
+ ratio = SequenceMatcher(None, heading.normalized_text, norm_target).ratio()
62
+
63
+ if ratio > best_ratio and ratio >= threshold:
64
+ best_ratio = ratio
65
+ best_heading = heading
66
+
67
+ if best_heading is not None:
68
+ return HeadingMatch(heading=best_heading, confidence=best_ratio)
69
+ return None
70
+
71
+
72
+ def find_close_matches(
73
+ headings: HeadingList,
74
+ target: str,
75
+ threshold: float = 0.6,
76
+ max_matches: int = 3,
77
+ ) -> list[CloseMatch]:
78
+ """Find headings that are close matches to the target.
79
+
80
+ Used for error messages when no exact match is found.
81
+
82
+ Args:
83
+ headings: List of MarkdownHeading objects
84
+ target: The target heading to match
85
+ threshold: Minimum similarity ratio for inclusion
86
+ max_matches: Maximum number of matches to return
87
+
88
+ Returns:
89
+ List of CloseMatch objects, sorted by confidence descending
90
+ """
91
+ norm_target = target.lstrip("#").strip().lower()
92
+ matches: list[CloseMatch] = []
93
+
94
+ for heading in headings:
95
+ ratio = SequenceMatcher(None, heading.normalized_text, norm_target).ratio()
96
+ if ratio >= threshold:
97
+ matches.append(CloseMatch(heading_text=heading.text, confidence=ratio))
98
+
99
+ # Sort by confidence descending
100
+ matches.sort(key=lambda x: x.confidence, reverse=True)
101
+ return matches[:max_matches]
102
+
103
+
104
+ def find_section_bounds(
105
+ lines: list[str],
106
+ heading_line_num: int,
107
+ heading_level: int,
108
+ ) -> tuple[int, int]:
109
+ """Find the boundaries of a section.
110
+
111
+ The section includes everything from the heading to the next heading
112
+ at the same or higher level (exclusive), or end of file.
113
+
114
+ Args:
115
+ lines: All lines of the file
116
+ heading_line_num: Line number of the section heading
117
+ heading_level: Level of the section heading (1-6)
118
+
119
+ Returns:
120
+ Tuple of (start_line, end_line) where end_line is exclusive
121
+ """
122
+ start = heading_line_num
123
+ end = len(lines) # Default to EOF
124
+
125
+ for i in range(heading_line_num + 1, len(lines)):
126
+ level = get_heading_level(lines[i])
127
+ if level is not None and level <= heading_level:
128
+ end = i
129
+ break
130
+
131
+ return (start, end)
132
+
133
+
134
+ def detect_line_ending(content: str) -> str:
135
+ """Detect the line ending style used in the content.
136
+
137
+ Args:
138
+ content: The file content
139
+
140
+ Returns:
141
+ The line ending string ('\\r\\n' or '\\n')
142
+ """
143
+ if "\r\n" in content:
144
+ return "\r\n"
145
+ return "\n"
146
+
147
+
148
+ def normalize_section_content(content: str) -> str:
149
+ """Normalize content to have no leading whitespace and single trailing newline.
150
+
151
+ Args:
152
+ content: The content to normalize
153
+
154
+ Returns:
155
+ Normalized content
156
+ """
157
+ return content.strip() + "\n"
File without changes