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
shotgun/cli/specify.py DELETED
@@ -1,70 +0,0 @@
1
- """Specify command for shotgun CLI."""
2
-
3
- import asyncio
4
- from typing import Annotated
5
-
6
- import typer
7
-
8
- from shotgun.agents.config import ProviderType
9
- from shotgun.agents.models import AgentRuntimeOptions
10
- from shotgun.agents.specify import (
11
- create_specify_agent,
12
- run_specify_agent,
13
- )
14
- from shotgun.cli.error_handler import print_agent_error
15
- from shotgun.exceptions import ErrorNotPickedUpBySentry
16
- from shotgun.logging_config import get_logger
17
-
18
- app = typer.Typer(
19
- name="specify", help="Generate comprehensive specifications", no_args_is_help=True
20
- )
21
- logger = get_logger(__name__)
22
-
23
-
24
- @app.callback(invoke_without_command=True)
25
- def specify(
26
- requirement: Annotated[
27
- str, typer.Argument(help="Requirement or feature to specify")
28
- ],
29
- non_interactive: Annotated[
30
- bool,
31
- typer.Option(
32
- "--non-interactive", "-n", help="Disable user interaction (for CI/CD)"
33
- ),
34
- ] = False,
35
- provider: Annotated[
36
- ProviderType | None,
37
- typer.Option("--provider", "-p", help="AI provider to use (overrides default)"),
38
- ] = None,
39
- ) -> None:
40
- """Generate comprehensive specifications for software features and systems.
41
-
42
- This command creates detailed technical specifications including requirements,
43
- architecture, implementation details, and acceptance criteria based on your
44
- provided requirement or feature description.
45
- """
46
-
47
- logger.info("📝 Specification Requirement: %s", requirement)
48
-
49
- # Create agent dependencies
50
- agent_runtime_options = AgentRuntimeOptions(interactive_mode=not non_interactive)
51
-
52
- # Create the specify agent with deps and provider
53
- agent, deps = asyncio.run(create_specify_agent(agent_runtime_options, provider))
54
-
55
- # Start specification process with error handling
56
- logger.info("📋 Starting specification generation...")
57
-
58
- async def async_specify() -> None:
59
- try:
60
- result = await run_specify_agent(agent, requirement, deps)
61
- logger.info("✅ Specification Complete!")
62
- logger.info("📋 Results:")
63
- logger.info("%s", result.output)
64
- except ErrorNotPickedUpBySentry as e:
65
- print_agent_error(e)
66
- except Exception as e:
67
- logger.exception("Unexpected error in specify command")
68
- print(f"⚠️ An unexpected error occurred: {str(e)}")
69
-
70
- asyncio.run(async_specify())
shotgun/cli/tasks.py DELETED
@@ -1,78 +0,0 @@
1
- """Tasks command for shotgun CLI."""
2
-
3
- import asyncio
4
- from typing import Annotated
5
-
6
- import typer
7
-
8
- from shotgun.agents.config import ProviderType
9
- from shotgun.agents.models import AgentRuntimeOptions
10
- from shotgun.agents.tasks import (
11
- create_tasks_agent,
12
- run_tasks_agent,
13
- )
14
- from shotgun.cli.error_handler import print_agent_error
15
- from shotgun.exceptions import ErrorNotPickedUpBySentry
16
- from shotgun.logging_config import get_logger
17
- from shotgun.posthog_telemetry import track_event
18
-
19
- app = typer.Typer(name="tasks", help="Generate task lists with agentic approach")
20
- logger = get_logger(__name__)
21
-
22
-
23
- @app.callback(invoke_without_command=True)
24
- def tasks(
25
- instruction: Annotated[
26
- str, typer.Argument(help="Task creation instruction or project description")
27
- ],
28
- non_interactive: Annotated[
29
- bool,
30
- typer.Option(
31
- "--non-interactive", "-n", help="Disable user interaction (for CI/CD)"
32
- ),
33
- ] = False,
34
- provider: Annotated[
35
- ProviderType | None,
36
- typer.Option("--provider", "-p", help="AI provider to use (overrides default)"),
37
- ] = None,
38
- ) -> None:
39
- """Generate actionable task lists based on existing research and plans.
40
-
41
- This command creates detailed task breakdowns using AI agents that analyze
42
- your research and plans to generate prioritized, actionable tasks with
43
- acceptance criteria and effort estimates.
44
- """
45
-
46
- logger.info("📋 Task Creation Instruction: %s", instruction)
47
-
48
- # Track tasks command usage
49
- track_event(
50
- "tasks_command",
51
- {
52
- "non_interactive": non_interactive,
53
- "provider": provider.value if provider else "default",
54
- },
55
- )
56
-
57
- # Create agent dependencies
58
- agent_runtime_options = AgentRuntimeOptions(interactive_mode=not non_interactive)
59
-
60
- # Create the tasks agent with deps and provider
61
- agent, deps = asyncio.run(create_tasks_agent(agent_runtime_options, provider))
62
-
63
- # Start task creation process with error handling
64
- logger.info("🎯 Starting task creation...")
65
-
66
- async def async_tasks() -> None:
67
- try:
68
- result = await run_tasks_agent(agent, instruction, deps)
69
- logger.info("✅ Task Creation Complete!")
70
- logger.info("📋 Results:")
71
- logger.info("%s", result.output)
72
- except ErrorNotPickedUpBySentry as e:
73
- print_agent_error(e)
74
- except Exception as e:
75
- logger.exception("Unexpected error in tasks command")
76
- print(f"⚠️ An unexpected error occurred: {str(e)}")
77
-
78
- asyncio.run(async_tasks())
@@ -1,232 +0,0 @@
1
- """Sentry observability setup for Shotgun."""
2
-
3
- from pathlib import Path
4
- from typing import Any
5
-
6
- from shotgun import __version__
7
- from shotgun.logging_config import get_early_logger
8
- from shotgun.settings import settings
9
-
10
- # Use early logger to prevent automatic StreamHandler creation
11
- logger = get_early_logger(__name__)
12
-
13
-
14
- def _scrub_path(path: str) -> str:
15
- """Scrub sensitive information from file paths.
16
-
17
- Removes home directory and current working directory prefixes to prevent
18
- leaking usernames that might be part of the path.
19
-
20
- Args:
21
- path: The file path to scrub
22
-
23
- Returns:
24
- The scrubbed path with sensitive prefixes removed
25
- """
26
- if not path:
27
- return path
28
-
29
- try:
30
- # Get home and cwd as Path objects for comparison
31
- home = Path.home()
32
- cwd = Path.cwd()
33
-
34
- # Convert path to Path object
35
- path_obj = Path(path)
36
-
37
- # Try to make path relative to cwd first (most common case)
38
- try:
39
- relative_to_cwd = path_obj.relative_to(cwd)
40
- return str(relative_to_cwd)
41
- except ValueError:
42
- pass
43
-
44
- # Try to replace home directory with ~
45
- try:
46
- relative_to_home = path_obj.relative_to(home)
47
- return f"~/{relative_to_home}"
48
- except ValueError:
49
- pass
50
-
51
- # If path is absolute but not under cwd or home, just return filename
52
- if path_obj.is_absolute():
53
- return path_obj.name
54
-
55
- # Return as-is if already relative
56
- return path
57
-
58
- except Exception:
59
- # If anything goes wrong, return the original path
60
- # Better to leak a path than break error reporting
61
- return path
62
-
63
-
64
- def _scrub_sensitive_paths(event: dict[str, Any]) -> None:
65
- """Scrub sensitive paths from Sentry event data.
66
-
67
- Modifies the event in-place to remove:
68
- - Home directory paths (might contain usernames)
69
- - Current working directory paths (might contain usernames)
70
- - Server name/hostname
71
- - Paths in sys.argv
72
-
73
- Args:
74
- event: The Sentry event dictionary to scrub
75
- """
76
- extra = event.get("extra", {})
77
- if "sys.argv" in extra:
78
- argv = extra["sys.argv"]
79
- if isinstance(argv, list):
80
- extra["sys.argv"] = [
81
- _scrub_path(arg) if isinstance(arg, str) else arg for arg in argv
82
- ]
83
-
84
- # Scrub server name if present
85
- if "server_name" in event:
86
- event["server_name"] = ""
87
-
88
- # Scrub contexts that might contain paths
89
- if "contexts" in event:
90
- contexts = event["contexts"]
91
- # Remove runtime context if it has CWD
92
- if "runtime" in contexts:
93
- if "cwd" in contexts["runtime"]:
94
- del contexts["runtime"]["cwd"]
95
- # Scrub sys.argv to remove paths
96
- if "sys.argv" in contexts["runtime"]:
97
- argv = contexts["runtime"]["sys.argv"]
98
- if isinstance(argv, list):
99
- contexts["runtime"]["sys.argv"] = [
100
- _scrub_path(arg) if isinstance(arg, str) else arg
101
- for arg in argv
102
- ]
103
-
104
- # Scrub exception stack traces
105
- if "exception" in event and "values" in event["exception"]:
106
- for exception in event["exception"]["values"]:
107
- if "stacktrace" in exception and "frames" in exception["stacktrace"]:
108
- for frame in exception["stacktrace"]["frames"]:
109
- # Scrub file paths
110
- if "abs_path" in frame:
111
- frame["abs_path"] = _scrub_path(frame["abs_path"])
112
- if "filename" in frame:
113
- frame["filename"] = _scrub_path(frame["filename"])
114
-
115
- # Scrub local variables that might contain paths
116
- if "vars" in frame:
117
- for var_name, var_value in frame["vars"].items():
118
- if isinstance(var_value, str):
119
- frame["vars"][var_name] = _scrub_path(var_value)
120
-
121
- # Scrub breadcrumbs that might contain paths
122
- if "breadcrumbs" in event and "values" in event["breadcrumbs"]:
123
- for breadcrumb in event["breadcrumbs"]["values"]:
124
- if "data" in breadcrumb:
125
- for key, value in breadcrumb["data"].items():
126
- if isinstance(value, str):
127
- breadcrumb["data"][key] = _scrub_path(value)
128
-
129
-
130
- def setup_sentry_observability() -> bool:
131
- """Set up Sentry observability for error tracking.
132
-
133
- Returns:
134
- True if Sentry was successfully set up, False otherwise
135
- """
136
- try:
137
- import sentry_sdk
138
-
139
- # Check if Sentry is already initialized
140
- if sentry_sdk.is_initialized():
141
- logger.debug("Sentry is already initialized, skipping")
142
- return True
143
-
144
- # Get DSN from settings (handles build constants + env vars automatically)
145
- dsn = settings.telemetry.sentry_dsn
146
-
147
- if not dsn:
148
- logger.debug("No Sentry DSN configured, skipping Sentry initialization")
149
- return False
150
-
151
- logger.debug("Using Sentry DSN from settings, proceeding with setup")
152
-
153
- # Determine environment based on version
154
- # Dev versions contain "dev", "rc", "alpha", "beta"
155
- if any(marker in __version__ for marker in ["dev", "rc", "alpha", "beta"]):
156
- environment = "development"
157
- else:
158
- environment = "production"
159
-
160
- def before_send(event: Any, hint: dict[str, Any]) -> Any:
161
- """Filter out user-actionable errors and scrub sensitive paths.
162
-
163
- User-actionable errors (like context size limits) are expected conditions
164
- that users need to resolve, not bugs that need tracking.
165
-
166
- Also scrubs sensitive information like usernames from file paths and
167
- working directories to protect user privacy.
168
- """
169
-
170
- log_record = hint.get("log_record")
171
- if log_record:
172
- # Scrub pathname using the helper function
173
- log_record.pathname = _scrub_path(log_record.pathname)
174
-
175
- # Scrub traceback text if it exists
176
- if hasattr(log_record, "exc_text") and isinstance(
177
- log_record.exc_text, str
178
- ):
179
- # Replace home directory in traceback text
180
- home = Path.home()
181
- log_record.exc_text = log_record.exc_text.replace(str(home), "~")
182
-
183
- if "exc_info" in hint:
184
- _, exc_value, _ = hint["exc_info"]
185
- from shotgun.exceptions import ErrorNotPickedUpBySentry
186
-
187
- if isinstance(exc_value, ErrorNotPickedUpBySentry):
188
- # Don't send to Sentry - this is user-actionable, not a bug
189
- return None
190
-
191
- # Scrub sensitive paths from the event
192
- _scrub_sensitive_paths(event)
193
- return event
194
-
195
- # Initialize Sentry
196
- sentry_sdk.init(
197
- dsn=dsn,
198
- release=f"shotgun-sh@{__version__}",
199
- environment=environment,
200
- send_default_pii=False, # Privacy-first: never send PII
201
- server_name="", # Privacy: don't send hostname (may contain username)
202
- traces_sample_rate=0.1 if environment == "production" else 1.0,
203
- profiles_sample_rate=0.1 if environment == "production" else 1.0,
204
- before_send=before_send,
205
- )
206
-
207
- # Set user context with anonymous shotgun instance ID from config
208
- try:
209
- import asyncio
210
-
211
- from shotgun.agents.config import get_config_manager
212
-
213
- config_manager = get_config_manager()
214
- shotgun_instance_id = asyncio.run(config_manager.get_shotgun_instance_id())
215
- sentry_sdk.set_user({"id": shotgun_instance_id})
216
- logger.debug("Sentry user context set with anonymous ID")
217
- except Exception as e:
218
- logger.warning("Failed to set Sentry user context: %s", e)
219
-
220
- logger.debug(
221
- "Sentry observability configured successfully (environment: %s, version: %s)",
222
- environment,
223
- __version__,
224
- )
225
- return True
226
-
227
- except ImportError as e:
228
- logger.error("Sentry SDK not available: %s", e)
229
- return False
230
- except Exception as e:
231
- logger.warning("Failed to setup Sentry observability: %s", e)
232
- return False