shotgun-sh 0.2.29.dev2__py3-none-any.whl → 0.6.1.dev1__py3-none-any.whl

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/agents/agent_manager.py +497 -30
  2. shotgun/agents/cancellation.py +103 -0
  3. shotgun/agents/common.py +90 -77
  4. shotgun/agents/config/README.md +0 -1
  5. shotgun/agents/config/manager.py +52 -8
  6. shotgun/agents/config/models.py +48 -45
  7. shotgun/agents/config/provider.py +44 -29
  8. shotgun/agents/conversation/history/file_content_deduplication.py +66 -43
  9. shotgun/agents/conversation/history/token_counting/base.py +51 -9
  10. shotgun/agents/export.py +12 -13
  11. shotgun/agents/file_read.py +176 -0
  12. shotgun/agents/messages.py +15 -3
  13. shotgun/agents/models.py +90 -2
  14. shotgun/agents/plan.py +12 -13
  15. shotgun/agents/research.py +13 -10
  16. shotgun/agents/router/__init__.py +47 -0
  17. shotgun/agents/router/models.py +384 -0
  18. shotgun/agents/router/router.py +185 -0
  19. shotgun/agents/router/tools/__init__.py +18 -0
  20. shotgun/agents/router/tools/delegation_tools.py +557 -0
  21. shotgun/agents/router/tools/plan_tools.py +403 -0
  22. shotgun/agents/runner.py +17 -2
  23. shotgun/agents/specify.py +12 -13
  24. shotgun/agents/tasks.py +12 -13
  25. shotgun/agents/tools/__init__.py +8 -0
  26. shotgun/agents/tools/codebase/directory_lister.py +27 -39
  27. shotgun/agents/tools/codebase/file_read.py +26 -35
  28. shotgun/agents/tools/codebase/query_graph.py +9 -0
  29. shotgun/agents/tools/codebase/retrieve_code.py +9 -0
  30. shotgun/agents/tools/file_management.py +81 -3
  31. shotgun/agents/tools/file_read_tools/__init__.py +7 -0
  32. shotgun/agents/tools/file_read_tools/multimodal_file_read.py +167 -0
  33. shotgun/agents/tools/markdown_tools/__init__.py +62 -0
  34. shotgun/agents/tools/markdown_tools/insert_section.py +148 -0
  35. shotgun/agents/tools/markdown_tools/models.py +86 -0
  36. shotgun/agents/tools/markdown_tools/remove_section.py +114 -0
  37. shotgun/agents/tools/markdown_tools/replace_section.py +119 -0
  38. shotgun/agents/tools/markdown_tools/utils.py +453 -0
  39. shotgun/agents/tools/registry.py +41 -0
  40. shotgun/agents/tools/web_search/__init__.py +1 -2
  41. shotgun/agents/tools/web_search/gemini.py +1 -3
  42. shotgun/agents/tools/web_search/openai.py +42 -23
  43. shotgun/attachments/__init__.py +41 -0
  44. shotgun/attachments/errors.py +60 -0
  45. shotgun/attachments/models.py +107 -0
  46. shotgun/attachments/parser.py +257 -0
  47. shotgun/attachments/processor.py +193 -0
  48. shotgun/cli/clear.py +2 -2
  49. shotgun/cli/codebase/commands.py +181 -65
  50. shotgun/cli/compact.py +2 -2
  51. shotgun/cli/context.py +2 -2
  52. shotgun/cli/run.py +90 -0
  53. shotgun/cli/spec/backup.py +2 -1
  54. shotgun/cli/spec/commands.py +2 -0
  55. shotgun/cli/spec/models.py +18 -0
  56. shotgun/cli/spec/pull_service.py +122 -68
  57. shotgun/codebase/__init__.py +2 -0
  58. shotgun/codebase/benchmarks/__init__.py +35 -0
  59. shotgun/codebase/benchmarks/benchmark_runner.py +309 -0
  60. shotgun/codebase/benchmarks/exporters.py +119 -0
  61. shotgun/codebase/benchmarks/formatters/__init__.py +49 -0
  62. shotgun/codebase/benchmarks/formatters/base.py +34 -0
  63. shotgun/codebase/benchmarks/formatters/json_formatter.py +106 -0
  64. shotgun/codebase/benchmarks/formatters/markdown.py +136 -0
  65. shotgun/codebase/benchmarks/models.py +129 -0
  66. shotgun/codebase/core/__init__.py +4 -0
  67. shotgun/codebase/core/call_resolution.py +91 -0
  68. shotgun/codebase/core/change_detector.py +11 -6
  69. shotgun/codebase/core/errors.py +159 -0
  70. shotgun/codebase/core/extractors/__init__.py +23 -0
  71. shotgun/codebase/core/extractors/base.py +138 -0
  72. shotgun/codebase/core/extractors/factory.py +63 -0
  73. shotgun/codebase/core/extractors/go/__init__.py +7 -0
  74. shotgun/codebase/core/extractors/go/extractor.py +122 -0
  75. shotgun/codebase/core/extractors/javascript/__init__.py +7 -0
  76. shotgun/codebase/core/extractors/javascript/extractor.py +132 -0
  77. shotgun/codebase/core/extractors/protocol.py +109 -0
  78. shotgun/codebase/core/extractors/python/__init__.py +7 -0
  79. shotgun/codebase/core/extractors/python/extractor.py +141 -0
  80. shotgun/codebase/core/extractors/rust/__init__.py +7 -0
  81. shotgun/codebase/core/extractors/rust/extractor.py +139 -0
  82. shotgun/codebase/core/extractors/types.py +15 -0
  83. shotgun/codebase/core/extractors/typescript/__init__.py +7 -0
  84. shotgun/codebase/core/extractors/typescript/extractor.py +92 -0
  85. shotgun/codebase/core/gitignore.py +252 -0
  86. shotgun/codebase/core/ingestor.py +644 -354
  87. shotgun/codebase/core/kuzu_compat.py +119 -0
  88. shotgun/codebase/core/language_config.py +239 -0
  89. shotgun/codebase/core/manager.py +256 -46
  90. shotgun/codebase/core/metrics_collector.py +310 -0
  91. shotgun/codebase/core/metrics_types.py +347 -0
  92. shotgun/codebase/core/parallel_executor.py +424 -0
  93. shotgun/codebase/core/work_distributor.py +254 -0
  94. shotgun/codebase/core/worker.py +768 -0
  95. shotgun/codebase/indexing_state.py +86 -0
  96. shotgun/codebase/models.py +94 -0
  97. shotgun/codebase/service.py +13 -0
  98. shotgun/exceptions.py +1 -1
  99. shotgun/main.py +2 -10
  100. shotgun/prompts/agents/export.j2 +2 -0
  101. shotgun/prompts/agents/file_read.j2 +48 -0
  102. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +20 -28
  103. shotgun/prompts/agents/partials/content_formatting.j2 +12 -33
  104. shotgun/prompts/agents/partials/interactive_mode.j2 +9 -32
  105. shotgun/prompts/agents/partials/router_delegation_mode.j2 +35 -0
  106. shotgun/prompts/agents/plan.j2 +43 -1
  107. shotgun/prompts/agents/research.j2 +75 -20
  108. shotgun/prompts/agents/router.j2 +713 -0
  109. shotgun/prompts/agents/specify.j2 +94 -4
  110. shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +14 -1
  111. shotgun/prompts/agents/state/system_state.j2 +24 -15
  112. shotgun/prompts/agents/tasks.j2 +77 -23
  113. shotgun/settings.py +44 -0
  114. shotgun/shotgun_web/shared_specs/upload_pipeline.py +38 -0
  115. shotgun/tui/app.py +90 -23
  116. shotgun/tui/commands/__init__.py +9 -1
  117. shotgun/tui/components/attachment_bar.py +87 -0
  118. shotgun/tui/components/mode_indicator.py +120 -25
  119. shotgun/tui/components/prompt_input.py +23 -28
  120. shotgun/tui/components/status_bar.py +5 -4
  121. shotgun/tui/dependencies.py +58 -8
  122. shotgun/tui/protocols.py +37 -0
  123. shotgun/tui/screens/chat/chat.tcss +24 -1
  124. shotgun/tui/screens/chat/chat_screen.py +1374 -211
  125. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +8 -4
  126. shotgun/tui/screens/chat_screen/attachment_hint.py +40 -0
  127. shotgun/tui/screens/chat_screen/command_providers.py +0 -97
  128. shotgun/tui/screens/chat_screen/history/agent_response.py +7 -3
  129. shotgun/tui/screens/chat_screen/history/chat_history.py +49 -6
  130. shotgun/tui/screens/chat_screen/history/formatters.py +75 -15
  131. shotgun/tui/screens/chat_screen/history/partial_response.py +11 -1
  132. shotgun/tui/screens/chat_screen/history/user_question.py +25 -3
  133. shotgun/tui/screens/chat_screen/messages.py +219 -0
  134. shotgun/tui/screens/database_locked_dialog.py +219 -0
  135. shotgun/tui/screens/database_timeout_dialog.py +158 -0
  136. shotgun/tui/screens/kuzu_error_dialog.py +135 -0
  137. shotgun/tui/screens/model_picker.py +14 -9
  138. shotgun/tui/screens/models.py +11 -0
  139. shotgun/tui/screens/shotgun_auth.py +50 -0
  140. shotgun/tui/screens/spec_pull.py +2 -0
  141. shotgun/tui/state/processing_state.py +19 -0
  142. shotgun/tui/utils/mode_progress.py +20 -86
  143. shotgun/tui/widgets/__init__.py +2 -1
  144. shotgun/tui/widgets/approval_widget.py +152 -0
  145. shotgun/tui/widgets/cascade_confirmation_widget.py +203 -0
  146. shotgun/tui/widgets/plan_panel.py +129 -0
  147. shotgun/tui/widgets/step_checkpoint_widget.py +180 -0
  148. shotgun/tui/widgets/widget_coordinator.py +18 -0
  149. shotgun/utils/file_system_utils.py +4 -1
  150. {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/METADATA +88 -34
  151. shotgun_sh-0.6.1.dev1.dist-info/RECORD +292 -0
  152. shotgun/cli/export.py +0 -81
  153. shotgun/cli/plan.py +0 -73
  154. shotgun/cli/research.py +0 -93
  155. shotgun/cli/specify.py +0 -70
  156. shotgun/cli/tasks.py +0 -78
  157. shotgun/tui/screens/onboarding.py +0 -580
  158. shotgun_sh-0.2.29.dev2.dist-info/RECORD +0 -229
  159. {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/WHEEL +0 -0
  160. {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/entry_points.txt +0 -0
  161. {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,252 @@
1
+ """Gitignore pattern matching for codebase indexing.
2
+
3
+ This module provides functionality to read and apply .gitignore rules
4
+ to filter files during indexing, significantly improving performance
5
+ for large codebases that may contain ignored directories like venv,
6
+ node_modules, build artifacts, etc.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING
13
+
14
+ import pathspec
15
+
16
+ from shotgun.codebase.models import GitignoreStats
17
+ from shotgun.logging_config import get_logger
18
+
19
+ if TYPE_CHECKING:
20
+ from pathspec import PathSpec
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ class GitignoreManager:
26
+ """Manages gitignore patterns for a repository.
27
+
28
+ This class loads and caches gitignore patterns from:
29
+ 1. The repository's .gitignore file
30
+ 2. Any .gitignore files in subdirectories (hierarchical gitignore)
31
+ 3. Global gitignore patterns (optional)
32
+
33
+ Usage:
34
+ manager = GitignoreManager(repo_path)
35
+ if manager.is_ignored("path/to/file.py"):
36
+ # Skip this file
37
+ pass
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ repo_path: Path,
43
+ load_nested: bool = True,
44
+ respect_gitignore: bool = True,
45
+ ):
46
+ """Initialize the gitignore manager.
47
+
48
+ Args:
49
+ repo_path: Root path of the repository
50
+ load_nested: Whether to load .gitignore files from subdirectories
51
+ respect_gitignore: Whether to respect .gitignore at all (if False, nothing is ignored)
52
+ """
53
+ self.repo_path = repo_path.resolve()
54
+ self.load_nested = load_nested
55
+ self.respect_gitignore = respect_gitignore
56
+
57
+ # Cache for PathSpec objects by directory
58
+ self._specs: dict[Path, PathSpec] = {}
59
+
60
+ # Combined spec for the root gitignore
61
+ self._root_spec: PathSpec | None = None
62
+
63
+ # Statistics for debugging
64
+ self.stats = GitignoreStats()
65
+
66
+ if respect_gitignore:
67
+ self._load_gitignore_files()
68
+
69
+ def _load_gitignore_files(self) -> None:
70
+ """Load all gitignore files in the repository."""
71
+ root_gitignore = self.repo_path / ".gitignore"
72
+
73
+ if root_gitignore.exists():
74
+ self._root_spec = self._load_gitignore_file(root_gitignore)
75
+ self.stats.gitignore_files_loaded += 1
76
+ logger.debug(
77
+ f"Loaded root .gitignore with patterns - "
78
+ f"path: {root_gitignore}, patterns: {self.stats.patterns_loaded}"
79
+ )
80
+
81
+ if self.load_nested:
82
+ # Find all nested .gitignore files
83
+ for gitignore_path in self.repo_path.rglob(".gitignore"):
84
+ if gitignore_path.parent == self.repo_path:
85
+ continue # Skip root, already loaded
86
+
87
+ spec = self._load_gitignore_file(gitignore_path)
88
+ if spec:
89
+ self._specs[gitignore_path.parent] = spec
90
+ self.stats.gitignore_files_loaded += 1
91
+
92
+ if self._specs:
93
+ logger.debug(f"Loaded {len(self._specs)} nested .gitignore files")
94
+
95
+ def _load_gitignore_file(self, gitignore_path: Path) -> PathSpec | None:
96
+ """Load patterns from a single gitignore file.
97
+
98
+ Args:
99
+ gitignore_path: Path to the .gitignore file
100
+
101
+ Returns:
102
+ PathSpec object or None if file couldn't be loaded
103
+ """
104
+ try:
105
+ with open(gitignore_path, encoding="utf-8", errors="ignore") as f:
106
+ patterns = f.read().splitlines()
107
+
108
+ # Filter out empty lines and comments
109
+ valid_patterns = [
110
+ p.strip()
111
+ for p in patterns
112
+ if p.strip() and not p.strip().startswith("#")
113
+ ]
114
+
115
+ if not valid_patterns:
116
+ return None
117
+
118
+ self.stats.patterns_loaded += len(valid_patterns)
119
+
120
+ return pathspec.PathSpec.from_lines("gitwildmatch", valid_patterns)
121
+ except Exception as e:
122
+ logger.warning(f"Failed to load gitignore: {gitignore_path}, error: {e}")
123
+ return None
124
+
125
+ def is_ignored(self, path: str | Path) -> bool:
126
+ """Check if a path should be ignored based on gitignore rules.
127
+
128
+ Args:
129
+ path: Path to check (relative to repo root or absolute)
130
+
131
+ Returns:
132
+ True if the path should be ignored
133
+ """
134
+ if not self.respect_gitignore:
135
+ return False
136
+
137
+ self.stats.files_checked += 1
138
+
139
+ # Convert to Path and make relative to repo root
140
+ path_obj = Path(path) if isinstance(path, str) else path
141
+
142
+ if path_obj.is_absolute():
143
+ # Resolve to handle symlinks (e.g., /var -> /private/var on macOS)
144
+ try:
145
+ resolved_path = path_obj.resolve()
146
+ path_obj = resolved_path.relative_to(self.repo_path)
147
+ except ValueError:
148
+ # Path is not under repo_path
149
+ return False
150
+
151
+ # Convert to string with forward slashes for consistency
152
+ path_str = str(path_obj).replace("\\", "/")
153
+
154
+ # Check root gitignore first
155
+ if self._root_spec and self._root_spec.match_file(path_str):
156
+ self.stats.files_ignored += 1
157
+ return True
158
+
159
+ # Check nested gitignore files
160
+ if self.load_nested:
161
+ # Walk up the directory tree to find applicable gitignore files
162
+ current_dir = (self.repo_path / path_obj).parent
163
+ while current_dir >= self.repo_path:
164
+ if current_dir in self._specs:
165
+ # Make path relative to this gitignore's directory
166
+ try:
167
+ rel_path = path_obj.relative_to(
168
+ current_dir.relative_to(self.repo_path)
169
+ )
170
+ rel_path_str = str(rel_path).replace("\\", "/")
171
+ if self._specs[current_dir].match_file(rel_path_str):
172
+ self.stats.files_ignored += 1
173
+ return True
174
+ except ValueError:
175
+ pass
176
+ current_dir = current_dir.parent
177
+
178
+ return False
179
+
180
+ def is_directory_ignored(self, path: str | Path) -> bool:
181
+ """Check if a directory should be ignored.
182
+
183
+ For directories, we add a trailing slash to match gitignore semantics.
184
+
185
+ Args:
186
+ path: Directory path to check
187
+
188
+ Returns:
189
+ True if the directory should be ignored
190
+ """
191
+ if not self.respect_gitignore:
192
+ return False
193
+
194
+ # Convert to Path and make relative to repo root
195
+ path_obj = Path(path) if isinstance(path, str) else path
196
+
197
+ if path_obj.is_absolute():
198
+ try:
199
+ path_obj = path_obj.relative_to(self.repo_path)
200
+ except ValueError:
201
+ return False
202
+
203
+ # Check both with and without trailing slash
204
+ path_str = str(path_obj).replace("\\", "/")
205
+ path_str_dir = path_str.rstrip("/") + "/"
206
+
207
+ # Check root gitignore
208
+ if self._root_spec:
209
+ if self._root_spec.match_file(path_str) or self._root_spec.match_file(
210
+ path_str_dir
211
+ ):
212
+ logger.debug(f"Directory ignored by root .gitignore: {path_str}")
213
+ return True
214
+
215
+ return False
216
+
217
+ def filter_paths(self, paths: list[Path]) -> list[Path]:
218
+ """Filter a list of paths, removing ignored ones.
219
+
220
+ Args:
221
+ paths: List of paths to filter
222
+
223
+ Returns:
224
+ List of paths that are not ignored
225
+ """
226
+ return [p for p in paths if not self.is_ignored(p)]
227
+
228
+ def get_stats_summary(self) -> str:
229
+ """Get a summary of gitignore statistics.
230
+
231
+ Returns:
232
+ Human-readable statistics string
233
+ """
234
+ return (
235
+ f"Gitignore stats: "
236
+ f"{self.stats.gitignore_files_loaded} files loaded, "
237
+ f"{self.stats.patterns_loaded} patterns, "
238
+ f"{self.stats.files_checked} paths checked, "
239
+ f"{self.stats.files_ignored} ignored"
240
+ )
241
+
242
+
243
+ def load_gitignore_for_repo(repo_path: Path | str) -> GitignoreManager:
244
+ """Convenience function to create a GitignoreManager for a repository.
245
+
246
+ Args:
247
+ repo_path: Path to the repository root
248
+
249
+ Returns:
250
+ Configured GitignoreManager
251
+ """
252
+ return GitignoreManager(Path(repo_path))