shotgun-sh 0.4.0.dev1__py3-none-any.whl → 0.6.2__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.
Files changed (135) hide show
  1. shotgun/agents/agent_manager.py +307 -8
  2. shotgun/agents/cancellation.py +103 -0
  3. shotgun/agents/common.py +12 -0
  4. shotgun/agents/config/README.md +0 -1
  5. shotgun/agents/config/manager.py +10 -7
  6. shotgun/agents/config/models.py +5 -27
  7. shotgun/agents/config/provider.py +44 -27
  8. shotgun/agents/conversation/history/token_counting/base.py +51 -9
  9. shotgun/agents/file_read.py +176 -0
  10. shotgun/agents/messages.py +15 -3
  11. shotgun/agents/models.py +24 -1
  12. shotgun/agents/router/models.py +8 -0
  13. shotgun/agents/router/tools/delegation_tools.py +55 -1
  14. shotgun/agents/router/tools/plan_tools.py +88 -7
  15. shotgun/agents/runner.py +17 -2
  16. shotgun/agents/tools/__init__.py +8 -0
  17. shotgun/agents/tools/codebase/directory_lister.py +27 -39
  18. shotgun/agents/tools/codebase/file_read.py +26 -35
  19. shotgun/agents/tools/codebase/query_graph.py +9 -0
  20. shotgun/agents/tools/codebase/retrieve_code.py +9 -0
  21. shotgun/agents/tools/file_management.py +32 -2
  22. shotgun/agents/tools/file_read_tools/__init__.py +7 -0
  23. shotgun/agents/tools/file_read_tools/multimodal_file_read.py +167 -0
  24. shotgun/agents/tools/markdown_tools/__init__.py +62 -0
  25. shotgun/agents/tools/markdown_tools/insert_section.py +148 -0
  26. shotgun/agents/tools/markdown_tools/models.py +86 -0
  27. shotgun/agents/tools/markdown_tools/remove_section.py +114 -0
  28. shotgun/agents/tools/markdown_tools/replace_section.py +119 -0
  29. shotgun/agents/tools/markdown_tools/utils.py +453 -0
  30. shotgun/agents/tools/registry.py +44 -6
  31. shotgun/agents/tools/web_search/openai.py +42 -23
  32. shotgun/attachments/__init__.py +41 -0
  33. shotgun/attachments/errors.py +60 -0
  34. shotgun/attachments/models.py +107 -0
  35. shotgun/attachments/parser.py +257 -0
  36. shotgun/attachments/processor.py +193 -0
  37. shotgun/build_constants.py +4 -7
  38. shotgun/cli/clear.py +2 -2
  39. shotgun/cli/codebase/commands.py +181 -65
  40. shotgun/cli/compact.py +2 -2
  41. shotgun/cli/context.py +2 -2
  42. shotgun/cli/error_handler.py +2 -2
  43. shotgun/cli/run.py +90 -0
  44. shotgun/cli/spec/backup.py +2 -1
  45. shotgun/codebase/__init__.py +2 -0
  46. shotgun/codebase/benchmarks/__init__.py +35 -0
  47. shotgun/codebase/benchmarks/benchmark_runner.py +309 -0
  48. shotgun/codebase/benchmarks/exporters.py +119 -0
  49. shotgun/codebase/benchmarks/formatters/__init__.py +49 -0
  50. shotgun/codebase/benchmarks/formatters/base.py +34 -0
  51. shotgun/codebase/benchmarks/formatters/json_formatter.py +106 -0
  52. shotgun/codebase/benchmarks/formatters/markdown.py +136 -0
  53. shotgun/codebase/benchmarks/models.py +129 -0
  54. shotgun/codebase/core/__init__.py +4 -0
  55. shotgun/codebase/core/call_resolution.py +91 -0
  56. shotgun/codebase/core/change_detector.py +11 -6
  57. shotgun/codebase/core/errors.py +159 -0
  58. shotgun/codebase/core/extractors/__init__.py +23 -0
  59. shotgun/codebase/core/extractors/base.py +138 -0
  60. shotgun/codebase/core/extractors/factory.py +63 -0
  61. shotgun/codebase/core/extractors/go/__init__.py +7 -0
  62. shotgun/codebase/core/extractors/go/extractor.py +122 -0
  63. shotgun/codebase/core/extractors/javascript/__init__.py +7 -0
  64. shotgun/codebase/core/extractors/javascript/extractor.py +132 -0
  65. shotgun/codebase/core/extractors/protocol.py +109 -0
  66. shotgun/codebase/core/extractors/python/__init__.py +7 -0
  67. shotgun/codebase/core/extractors/python/extractor.py +141 -0
  68. shotgun/codebase/core/extractors/rust/__init__.py +7 -0
  69. shotgun/codebase/core/extractors/rust/extractor.py +139 -0
  70. shotgun/codebase/core/extractors/types.py +15 -0
  71. shotgun/codebase/core/extractors/typescript/__init__.py +7 -0
  72. shotgun/codebase/core/extractors/typescript/extractor.py +92 -0
  73. shotgun/codebase/core/gitignore.py +252 -0
  74. shotgun/codebase/core/ingestor.py +644 -354
  75. shotgun/codebase/core/kuzu_compat.py +119 -0
  76. shotgun/codebase/core/language_config.py +239 -0
  77. shotgun/codebase/core/manager.py +256 -46
  78. shotgun/codebase/core/metrics_collector.py +310 -0
  79. shotgun/codebase/core/metrics_types.py +347 -0
  80. shotgun/codebase/core/parallel_executor.py +424 -0
  81. shotgun/codebase/core/work_distributor.py +254 -0
  82. shotgun/codebase/core/worker.py +768 -0
  83. shotgun/codebase/indexing_state.py +86 -0
  84. shotgun/codebase/models.py +94 -0
  85. shotgun/codebase/service.py +13 -0
  86. shotgun/exceptions.py +9 -9
  87. shotgun/main.py +3 -16
  88. shotgun/posthog_telemetry.py +165 -24
  89. shotgun/prompts/agents/file_read.j2 +48 -0
  90. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +19 -47
  91. shotgun/prompts/agents/partials/content_formatting.j2 +12 -33
  92. shotgun/prompts/agents/partials/interactive_mode.j2 +9 -32
  93. shotgun/prompts/agents/partials/router_delegation_mode.j2 +21 -22
  94. shotgun/prompts/agents/plan.j2 +14 -0
  95. shotgun/prompts/agents/router.j2 +531 -258
  96. shotgun/prompts/agents/specify.j2 +14 -0
  97. shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +14 -1
  98. shotgun/prompts/agents/state/system_state.j2 +13 -11
  99. shotgun/prompts/agents/tasks.j2 +14 -0
  100. shotgun/settings.py +49 -10
  101. shotgun/tui/app.py +149 -18
  102. shotgun/tui/commands/__init__.py +9 -1
  103. shotgun/tui/components/attachment_bar.py +87 -0
  104. shotgun/tui/components/prompt_input.py +25 -28
  105. shotgun/tui/components/status_bar.py +14 -7
  106. shotgun/tui/dependencies.py +3 -8
  107. shotgun/tui/protocols.py +18 -0
  108. shotgun/tui/screens/chat/chat.tcss +15 -0
  109. shotgun/tui/screens/chat/chat_screen.py +766 -235
  110. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +8 -4
  111. shotgun/tui/screens/chat_screen/attachment_hint.py +40 -0
  112. shotgun/tui/screens/chat_screen/command_providers.py +0 -10
  113. shotgun/tui/screens/chat_screen/history/chat_history.py +54 -14
  114. shotgun/tui/screens/chat_screen/history/formatters.py +22 -0
  115. shotgun/tui/screens/chat_screen/history/user_question.py +25 -3
  116. shotgun/tui/screens/database_locked_dialog.py +219 -0
  117. shotgun/tui/screens/database_timeout_dialog.py +158 -0
  118. shotgun/tui/screens/kuzu_error_dialog.py +135 -0
  119. shotgun/tui/screens/model_picker.py +1 -3
  120. shotgun/tui/screens/models.py +11 -0
  121. shotgun/tui/state/processing_state.py +19 -0
  122. shotgun/tui/widgets/widget_coordinator.py +18 -0
  123. shotgun/utils/file_system_utils.py +4 -1
  124. {shotgun_sh-0.4.0.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/METADATA +87 -34
  125. {shotgun_sh-0.4.0.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/RECORD +128 -79
  126. shotgun/cli/export.py +0 -81
  127. shotgun/cli/plan.py +0 -73
  128. shotgun/cli/research.py +0 -93
  129. shotgun/cli/specify.py +0 -70
  130. shotgun/cli/tasks.py +0 -78
  131. shotgun/sentry_telemetry.py +0 -232
  132. shotgun/tui/screens/onboarding.py +0 -584
  133. {shotgun_sh-0.4.0.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/WHEEL +0 -0
  134. {shotgun_sh-0.4.0.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/entry_points.txt +0 -0
  135. {shotgun_sh-0.4.0.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/licenses/LICENSE +0 -0
@@ -16,6 +16,8 @@ from rich.progress import (
16
16
  TimeElapsedColumn,
17
17
  )
18
18
 
19
+ from shotgun.codebase.benchmarks import BenchmarkRunner, MetricsExporter, get_formatter
20
+ from shotgun.codebase.benchmarks.formatters import MetricsDisplayOptions
19
21
  from shotgun.codebase.models import (
20
22
  CodebaseGraph,
21
23
  IndexProgress,
@@ -69,81 +71,195 @@ def index(
69
71
  format_type: Annotated[
70
72
  OutputFormat, typer.Option("--format", "-f", help="Output format")
71
73
  ] = OutputFormat.TEXT,
74
+ # Benchmark flags
75
+ benchmark: Annotated[
76
+ bool,
77
+ typer.Option("--benchmark", help="Enable benchmark mode with detailed metrics"),
78
+ ] = False,
79
+ iterations: Annotated[
80
+ int,
81
+ typer.Option(
82
+ "--benchmark-iterations",
83
+ help="Number of benchmark runs (requires --benchmark)",
84
+ ),
85
+ ] = 1,
86
+ warmup: Annotated[
87
+ int,
88
+ typer.Option(
89
+ "--benchmark-warmup", help="Number of warmup runs (requires --benchmark)"
90
+ ),
91
+ ] = 0,
92
+ benchmark_output: Annotated[
93
+ str,
94
+ typer.Option(
95
+ "--benchmark-output",
96
+ help="Benchmark output format: json|markdown",
97
+ ),
98
+ ] = "json",
99
+ benchmark_export: Annotated[
100
+ str | None,
101
+ typer.Option("--benchmark-export", help="Export metrics to file"),
102
+ ] = None,
103
+ show_files: Annotated[
104
+ bool,
105
+ typer.Option("--show-files", help="Show per-file metrics in benchmark output"),
106
+ ] = False,
107
+ show_workers: Annotated[
108
+ bool,
109
+ typer.Option(
110
+ "--show-workers", help="Show per-worker metrics in benchmark output"
111
+ ),
112
+ ] = False,
113
+ top_n: Annotated[
114
+ int | None,
115
+ typer.Option("--top-n", help="Show N slowest files (requires --benchmark)"),
116
+ ] = None,
117
+ sequential: Annotated[
118
+ bool,
119
+ typer.Option(
120
+ "--sequential", help="Force sequential mode (disable parallelization)"
121
+ ),
122
+ ] = False,
72
123
  ) -> None:
73
- """Index a new codebase."""
74
- sdk = CodebaseSDK()
124
+ """Index a new codebase.
125
+
126
+ By default, runs with a TUI progress display. Use --benchmark for detailed
127
+ metrics reporting.
128
+ """
75
129
  console = Console()
76
130
 
77
- # Create progress display
78
- progress = Progress(
79
- SpinnerColumn(),
80
- TextColumn("[bold blue]{task.description}"),
81
- BarColumn(),
82
- TaskProgressColumn(),
83
- TimeElapsedColumn(),
84
- console=console,
85
- )
86
-
87
- # Track tasks by phase
88
- tasks = {}
89
-
90
- def progress_callback(progress_info: IndexProgress) -> None:
91
- """Update progress display based on indexing phase."""
92
- phase = progress_info.phase
93
-
94
- # Create task if it doesn't exist
95
- if phase not in tasks:
96
- if progress_info.total is not None:
97
- tasks[phase] = progress.add_task(
98
- progress_info.phase_name, total=progress_info.total
99
- )
100
- else:
101
- # Indeterminate progress (spinner only)
102
- tasks[phase] = progress.add_task(progress_info.phase_name, total=None)
103
-
104
- task_id = tasks[phase]
105
-
106
- # Update task
107
- if progress_info.total is not None:
108
- progress.update(
109
- task_id,
110
- completed=progress_info.current,
111
- total=progress_info.total,
112
- description=f"[bold blue]{progress_info.phase_name}",
131
+ # Validate path
132
+ repo_path = Path(path).resolve()
133
+ if not repo_path.exists():
134
+ error_result = ErrorResult(error_message=f"Path does not exist: {repo_path}")
135
+ output_result(error_result, format_type)
136
+ raise typer.Exit(1)
137
+
138
+ # Benchmark mode
139
+ if benchmark:
140
+ try:
141
+ # Create and run benchmark
142
+ runner = BenchmarkRunner(
143
+ codebase_path=repo_path,
144
+ codebase_name=name,
145
+ iterations=iterations,
146
+ warmup_iterations=warmup,
147
+ parallel=not sequential,
148
+ collect_file_metrics=show_files or top_n is not None,
149
+ collect_worker_metrics=show_workers,
150
+ )
151
+
152
+ console.print(f"\n[bold blue]Starting benchmark: {name}[/bold blue]")
153
+ console.print(f"Path: {repo_path}")
154
+ console.print(f"Mode: {'Sequential' if sequential else 'Parallel'}")
155
+ console.print(f"Iterations: {iterations} ({warmup} warmup)")
156
+ console.print()
157
+
158
+ results = asyncio.run(runner.run())
159
+
160
+ # Format output
161
+ formatter = get_formatter(benchmark_output)
162
+ options = MetricsDisplayOptions(
163
+ show_phase_metrics=True,
164
+ show_worker_metrics=show_workers,
165
+ show_file_metrics=show_files or top_n is not None,
166
+ top_n_files=top_n,
113
167
  )
114
- else:
115
- # Just update description for indeterminate tasks
116
- progress.update(
117
- task_id,
118
- description=f"[bold blue]{progress_info.phase_name} ({progress_info.current} items)",
168
+ output = formatter.format_results(results, options)
169
+ console.print(output)
170
+
171
+ # Export if requested
172
+ if benchmark_export:
173
+ exporter = MetricsExporter()
174
+ exporter.export(results, Path(benchmark_export), options=options)
175
+ console.print(
176
+ f"\n[green]Metrics exported to: {benchmark_export}[/green]"
177
+ )
178
+
179
+ except Exception as e:
180
+ error_result = ErrorResult(
181
+ error_message=f"Benchmark error: {e}",
182
+ details=f"Full traceback:\n{traceback.format_exc()}",
119
183
  )
184
+ output_result(error_result, format_type)
185
+ raise typer.Exit(1) from e
186
+
187
+ else:
188
+ # Normal mode with TUI progress display
189
+ sdk = CodebaseSDK()
190
+
191
+ # Create progress display
192
+ progress = Progress(
193
+ SpinnerColumn(),
194
+ TextColumn("[bold blue]{task.description}"),
195
+ BarColumn(),
196
+ TaskProgressColumn(),
197
+ TimeElapsedColumn(),
198
+ console=console,
199
+ )
200
+
201
+ # Track tasks by phase
202
+ tasks = {}
120
203
 
121
- # Mark as complete if phase is done
122
- if progress_info.phase_complete:
204
+ def progress_callback(progress_info: IndexProgress) -> None:
205
+ """Update progress display based on indexing phase."""
206
+ phase = progress_info.phase
207
+
208
+ # Create task if it doesn't exist
209
+ if phase not in tasks:
210
+ if progress_info.total is not None:
211
+ tasks[phase] = progress.add_task(
212
+ progress_info.phase_name, total=progress_info.total
213
+ )
214
+ else:
215
+ # Indeterminate progress (spinner only)
216
+ tasks[phase] = progress.add_task(
217
+ progress_info.phase_name, total=None
218
+ )
219
+
220
+ task_id = tasks[phase]
221
+
222
+ # Update task
123
223
  if progress_info.total is not None:
124
- progress.update(task_id, completed=progress_info.total)
224
+ progress.update(
225
+ task_id,
226
+ completed=progress_info.current,
227
+ total=progress_info.total,
228
+ description=f"[bold blue]{progress_info.phase_name}",
229
+ )
230
+ else:
231
+ # Just update description for indeterminate tasks
232
+ progress.update(
233
+ task_id,
234
+ description=f"[bold blue]{progress_info.phase_name} ({progress_info.current} items)",
235
+ )
125
236
 
126
- try:
127
- repo_path = Path(path)
237
+ # Mark as complete if phase is done
238
+ if progress_info.phase_complete:
239
+ if progress_info.total is not None:
240
+ progress.update(task_id, completed=progress_info.total)
241
+
242
+ try:
243
+ # Run indexing with progress display
244
+ with progress:
245
+ result = asyncio.run(
246
+ sdk.index_codebase(
247
+ repo_path, name, progress_callback=progress_callback
248
+ )
249
+ )
128
250
 
129
- # Run indexing with progress display
130
- with progress:
131
- result = asyncio.run(
132
- sdk.index_codebase(repo_path, name, progress_callback=progress_callback)
251
+ output_result(result, format_type)
252
+ except InvalidPathError as e:
253
+ error_result = ErrorResult(error_message=str(e))
254
+ output_result(error_result, format_type)
255
+ raise typer.Exit(1) from e
256
+ except Exception as e:
257
+ error_result = ErrorResult(
258
+ error_message=f"Error indexing codebase: {e}",
259
+ details=f"Full traceback:\n{traceback.format_exc()}",
133
260
  )
134
-
135
- output_result(result, format_type)
136
- except InvalidPathError as e:
137
- error_result = ErrorResult(error_message=str(e))
138
- output_result(error_result, format_type)
139
- raise typer.Exit(1) from e
140
- except Exception as e:
141
- error_result = ErrorResult(
142
- error_message=f"Error indexing codebase: {e}",
143
- details=f"Full traceback:\n{traceback.format_exc()}",
144
- )
145
- output_result(error_result, format_type)
146
- raise typer.Exit(1) from e
261
+ output_result(error_result, format_type)
262
+ raise typer.Exit(1) from e
147
263
 
148
264
 
149
265
  @app.command()
shotgun/cli/compact.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  import asyncio
4
4
  import json
5
- from pathlib import Path
6
5
  from typing import Annotated, Any
7
6
 
8
7
  import typer
@@ -17,6 +16,7 @@ from shotgun.agents.conversation.history.token_estimation import (
17
16
  )
18
17
  from shotgun.cli.models import OutputFormat
19
18
  from shotgun.logging_config import get_logger
19
+ from shotgun.utils import get_shotgun_home
20
20
 
21
21
  app = typer.Typer(
22
22
  name="compact", help="Compact the conversation history", no_args_is_help=False
@@ -74,7 +74,7 @@ async def compact_conversation() -> dict[str, Any]:
74
74
  Dictionary with compaction statistics including before/after metrics
75
75
  """
76
76
  # Get conversation file path
77
- conversation_file = Path.home() / ".shotgun-sh" / "conversation.json"
77
+ conversation_file = get_shotgun_home() / "conversation.json"
78
78
 
79
79
  if not conversation_file.exists():
80
80
  raise FileNotFoundError(f"Conversation file not found at {conversation_file}")
shotgun/cli/context.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  import asyncio
4
4
  import json
5
- from pathlib import Path
6
5
  from typing import Annotated
7
6
 
8
7
  import httpx
@@ -19,6 +18,7 @@ from shotgun.agents.conversation import ConversationManager
19
18
  from shotgun.cli.models import OutputFormat
20
19
  from shotgun.llm_proxy import BudgetInfo, LiteLLMProxyClient
21
20
  from shotgun.logging_config import get_logger
21
+ from shotgun.utils import get_shotgun_home
22
22
 
23
23
  app = typer.Typer(
24
24
  name="context", help="Analyze conversation context usage", no_args_is_help=False
@@ -74,7 +74,7 @@ async def analyze_context() -> ContextAnalysisOutput:
74
74
  ContextAnalysisOutput with both markdown and JSON representations of the analysis
75
75
  """
76
76
  # Get conversation file path
77
- conversation_file = Path.home() / ".shotgun-sh" / "conversation.json"
77
+ conversation_file = get_shotgun_home() / "conversation.json"
78
78
 
79
79
  if not conversation_file.exists():
80
80
  raise FileNotFoundError(f"Conversation file not found at {conversation_file}")
@@ -6,12 +6,12 @@ by printing formatted messages to the console.
6
6
 
7
7
  from rich.console import Console
8
8
 
9
- from shotgun.exceptions import ErrorNotPickedUpBySentry
9
+ from shotgun.exceptions import UserActionableError
10
10
 
11
11
  console = Console(stderr=True)
12
12
 
13
13
 
14
- def print_agent_error(exception: ErrorNotPickedUpBySentry) -> None:
14
+ def print_agent_error(exception: UserActionableError) -> None:
15
15
  """Print an agent error to the console in yellow.
16
16
 
17
17
  Args:
shotgun/cli/run.py ADDED
@@ -0,0 +1,90 @@
1
+ """Run command for shotgun CLI - executes prompts using the Router agent."""
2
+
3
+ import asyncio
4
+ import traceback
5
+ from typing import Annotated
6
+
7
+ import typer
8
+
9
+ from shotgun.agents.config import ProviderType
10
+ from shotgun.agents.models import AgentRuntimeOptions
11
+ from shotgun.agents.router import (
12
+ RouterMode,
13
+ create_router_agent,
14
+ run_router_agent,
15
+ )
16
+ from shotgun.cli.error_handler import print_agent_error
17
+ from shotgun.exceptions import UserActionableError
18
+ from shotgun.logging_config import get_logger
19
+ from shotgun.posthog_telemetry import track_event
20
+
21
+ app = typer.Typer(
22
+ name="run", help="Run a prompt using the Router agent", no_args_is_help=True
23
+ )
24
+ logger = get_logger(__name__)
25
+
26
+
27
+ @app.callback(invoke_without_command=True)
28
+ def run(
29
+ prompt: Annotated[str, typer.Argument(help="The prompt to execute")],
30
+ non_interactive: Annotated[
31
+ bool,
32
+ typer.Option(
33
+ "--non-interactive", "-n", help="Disable user interaction (for CI/CD)"
34
+ ),
35
+ ] = False,
36
+ provider: Annotated[
37
+ ProviderType | None,
38
+ typer.Option("--provider", "-p", help="AI provider to use (overrides default)"),
39
+ ] = None,
40
+ ) -> None:
41
+ """Execute a prompt using the Router agent in drafting mode.
42
+
43
+ The Router agent orchestrates sub-agents (Research, Specify, Plan, Tasks, Export)
44
+ based on your prompt. In drafting mode, it auto-executes without confirmation.
45
+ """
46
+ logger.info("Running prompt: %s", prompt[:100])
47
+
48
+ try:
49
+ asyncio.run(async_run(prompt, non_interactive, provider))
50
+ except Exception as e:
51
+ logger.error("Error during execution: %s", str(e))
52
+ logger.debug("Full traceback:\n%s", traceback.format_exc())
53
+
54
+
55
+ async def async_run(
56
+ prompt: str,
57
+ non_interactive: bool,
58
+ provider: ProviderType | None = None,
59
+ ) -> None:
60
+ """Async implementation of the run command."""
61
+ track_event(
62
+ "run_command",
63
+ {
64
+ "non_interactive": non_interactive,
65
+ "provider": provider.value if provider else "default",
66
+ },
67
+ )
68
+
69
+ # Create agent runtime options
70
+ agent_runtime_options = AgentRuntimeOptions(
71
+ interactive_mode=not non_interactive,
72
+ )
73
+
74
+ # Create the router agent
75
+ agent, deps = await create_router_agent(agent_runtime_options, provider)
76
+
77
+ # Set drafting mode for CLI (auto-execute without confirmation)
78
+ deps.router_mode = RouterMode.DRAFTING
79
+
80
+ logger.info("Starting Router agent in drafting mode...")
81
+ try:
82
+ result = await run_router_agent(agent, prompt, deps)
83
+ print("Complete!")
84
+ print("Response:")
85
+ print(result.output)
86
+ except UserActionableError as e:
87
+ print_agent_error(e)
88
+ except Exception as e:
89
+ logger.exception("Unexpected error in run command")
90
+ print(f"An unexpected error occurred: {str(e)}")
@@ -6,11 +6,12 @@ from datetime import datetime, timezone
6
6
  from pathlib import Path
7
7
 
8
8
  from shotgun.logging_config import get_logger
9
+ from shotgun.utils import get_shotgun_home
9
10
 
10
11
  logger = get_logger(__name__)
11
12
 
12
13
  # Backup directory location
13
- BACKUP_DIR = Path.home() / ".shotgun-sh" / "backups"
14
+ BACKUP_DIR = get_shotgun_home() / "backups"
14
15
 
15
16
 
16
17
  async def create_backup(shotgun_dir: Path) -> str | None:
@@ -1,5 +1,6 @@
1
1
  """Shotgun codebase analysis and graph management."""
2
2
 
3
+ from shotgun.codebase.indexing_state import IndexingState
3
4
  from shotgun.codebase.models import CodebaseGraph, GraphStatus, QueryResult, QueryType
4
5
  from shotgun.codebase.service import CodebaseService
5
6
 
@@ -7,6 +8,7 @@ __all__ = [
7
8
  "CodebaseService",
8
9
  "CodebaseGraph",
9
10
  "GraphStatus",
11
+ "IndexingState",
10
12
  "QueryResult",
11
13
  "QueryType",
12
14
  ]
@@ -0,0 +1,35 @@
1
+ """Benchmark system for codebase indexing performance analysis.
2
+
3
+ This package provides tools for running benchmarks and reporting metrics
4
+ for the codebase indexing pipeline.
5
+ """
6
+
7
+ from shotgun.codebase.benchmarks.benchmark_runner import BenchmarkRunner
8
+ from shotgun.codebase.benchmarks.exporters import MetricsExporter
9
+ from shotgun.codebase.benchmarks.formatters import (
10
+ JsonFormatter,
11
+ MarkdownFormatter,
12
+ MetricsDisplayOptions,
13
+ get_formatter,
14
+ )
15
+ from shotgun.codebase.benchmarks.models import (
16
+ BenchmarkConfig,
17
+ BenchmarkMode,
18
+ BenchmarkResults,
19
+ BenchmarkRun,
20
+ OutputFormat,
21
+ )
22
+
23
+ __all__ = [
24
+ "BenchmarkConfig",
25
+ "BenchmarkMode",
26
+ "BenchmarkResults",
27
+ "BenchmarkRun",
28
+ "BenchmarkRunner",
29
+ "JsonFormatter",
30
+ "MarkdownFormatter",
31
+ "MetricsDisplayOptions",
32
+ "MetricsExporter",
33
+ "OutputFormat",
34
+ "get_formatter",
35
+ ]