shotgun-sh 0.2.8.dev2__py3-none-any.whl → 0.3.3.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.
Files changed (175) hide show
  1. shotgun/agents/agent_manager.py +382 -60
  2. shotgun/agents/common.py +15 -9
  3. shotgun/agents/config/README.md +89 -0
  4. shotgun/agents/config/__init__.py +10 -1
  5. shotgun/agents/config/constants.py +0 -6
  6. shotgun/agents/config/manager.py +383 -82
  7. shotgun/agents/config/models.py +122 -18
  8. shotgun/agents/config/provider.py +81 -15
  9. shotgun/agents/config/streaming_test.py +119 -0
  10. shotgun/agents/context_analyzer/__init__.py +28 -0
  11. shotgun/agents/context_analyzer/analyzer.py +475 -0
  12. shotgun/agents/context_analyzer/constants.py +9 -0
  13. shotgun/agents/context_analyzer/formatter.py +115 -0
  14. shotgun/agents/context_analyzer/models.py +212 -0
  15. shotgun/agents/conversation/__init__.py +18 -0
  16. shotgun/agents/conversation/filters.py +164 -0
  17. shotgun/agents/conversation/history/chunking.py +278 -0
  18. shotgun/agents/{history → conversation/history}/compaction.py +36 -5
  19. shotgun/agents/{history → conversation/history}/constants.py +5 -0
  20. shotgun/agents/conversation/history/file_content_deduplication.py +216 -0
  21. shotgun/agents/{history → conversation/history}/history_processors.py +380 -8
  22. shotgun/agents/{history → conversation/history}/token_counting/anthropic.py +25 -1
  23. shotgun/agents/{history → conversation/history}/token_counting/base.py +14 -3
  24. shotgun/agents/{history → conversation/history}/token_counting/openai.py +11 -1
  25. shotgun/agents/{history → conversation/history}/token_counting/sentencepiece_counter.py +8 -0
  26. shotgun/agents/{history → conversation/history}/token_counting/tokenizer_cache.py +3 -1
  27. shotgun/agents/{history → conversation/history}/token_counting/utils.py +0 -3
  28. shotgun/agents/{conversation_manager.py → conversation/manager.py} +36 -20
  29. shotgun/agents/{conversation_history.py → conversation/models.py} +8 -92
  30. shotgun/agents/error/__init__.py +11 -0
  31. shotgun/agents/error/models.py +19 -0
  32. shotgun/agents/export.py +2 -2
  33. shotgun/agents/plan.py +2 -2
  34. shotgun/agents/research.py +3 -3
  35. shotgun/agents/runner.py +230 -0
  36. shotgun/agents/specify.py +2 -2
  37. shotgun/agents/tasks.py +2 -2
  38. shotgun/agents/tools/codebase/codebase_shell.py +6 -0
  39. shotgun/agents/tools/codebase/directory_lister.py +6 -0
  40. shotgun/agents/tools/codebase/file_read.py +11 -2
  41. shotgun/agents/tools/codebase/query_graph.py +6 -0
  42. shotgun/agents/tools/codebase/retrieve_code.py +6 -0
  43. shotgun/agents/tools/file_management.py +27 -7
  44. shotgun/agents/tools/registry.py +217 -0
  45. shotgun/agents/tools/web_search/__init__.py +8 -8
  46. shotgun/agents/tools/web_search/anthropic.py +8 -2
  47. shotgun/agents/tools/web_search/gemini.py +7 -1
  48. shotgun/agents/tools/web_search/openai.py +8 -2
  49. shotgun/agents/tools/web_search/utils.py +2 -2
  50. shotgun/agents/usage_manager.py +16 -11
  51. shotgun/api_endpoints.py +7 -3
  52. shotgun/build_constants.py +2 -2
  53. shotgun/cli/clear.py +53 -0
  54. shotgun/cli/compact.py +188 -0
  55. shotgun/cli/config.py +8 -5
  56. shotgun/cli/context.py +154 -0
  57. shotgun/cli/error_handler.py +24 -0
  58. shotgun/cli/export.py +34 -34
  59. shotgun/cli/feedback.py +4 -2
  60. shotgun/cli/models.py +1 -0
  61. shotgun/cli/plan.py +34 -34
  62. shotgun/cli/research.py +18 -10
  63. shotgun/cli/spec/__init__.py +5 -0
  64. shotgun/cli/spec/backup.py +81 -0
  65. shotgun/cli/spec/commands.py +132 -0
  66. shotgun/cli/spec/models.py +48 -0
  67. shotgun/cli/spec/pull_service.py +219 -0
  68. shotgun/cli/specify.py +20 -19
  69. shotgun/cli/tasks.py +34 -34
  70. shotgun/cli/update.py +16 -2
  71. shotgun/codebase/core/change_detector.py +5 -3
  72. shotgun/codebase/core/code_retrieval.py +4 -2
  73. shotgun/codebase/core/ingestor.py +163 -15
  74. shotgun/codebase/core/manager.py +13 -4
  75. shotgun/codebase/core/nl_query.py +1 -1
  76. shotgun/codebase/models.py +2 -0
  77. shotgun/exceptions.py +357 -0
  78. shotgun/llm_proxy/__init__.py +17 -0
  79. shotgun/llm_proxy/client.py +215 -0
  80. shotgun/llm_proxy/models.py +137 -0
  81. shotgun/logging_config.py +60 -27
  82. shotgun/main.py +77 -11
  83. shotgun/posthog_telemetry.py +38 -29
  84. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +28 -2
  85. shotgun/prompts/agents/partials/interactive_mode.j2 +3 -3
  86. shotgun/prompts/agents/plan.j2 +16 -0
  87. shotgun/prompts/agents/research.j2 +16 -3
  88. shotgun/prompts/agents/specify.j2 +54 -1
  89. shotgun/prompts/agents/state/system_state.j2 +0 -2
  90. shotgun/prompts/agents/tasks.j2 +16 -0
  91. shotgun/prompts/history/chunk_summarization.j2 +34 -0
  92. shotgun/prompts/history/combine_summaries.j2 +53 -0
  93. shotgun/sdk/codebase.py +14 -3
  94. shotgun/sentry_telemetry.py +163 -16
  95. shotgun/settings.py +243 -0
  96. shotgun/shotgun_web/__init__.py +67 -1
  97. shotgun/shotgun_web/client.py +42 -1
  98. shotgun/shotgun_web/constants.py +46 -0
  99. shotgun/shotgun_web/exceptions.py +29 -0
  100. shotgun/shotgun_web/models.py +390 -0
  101. shotgun/shotgun_web/shared_specs/__init__.py +32 -0
  102. shotgun/shotgun_web/shared_specs/file_scanner.py +175 -0
  103. shotgun/shotgun_web/shared_specs/hasher.py +83 -0
  104. shotgun/shotgun_web/shared_specs/models.py +71 -0
  105. shotgun/shotgun_web/shared_specs/upload_pipeline.py +329 -0
  106. shotgun/shotgun_web/shared_specs/utils.py +34 -0
  107. shotgun/shotgun_web/specs_client.py +703 -0
  108. shotgun/shotgun_web/supabase_client.py +31 -0
  109. shotgun/telemetry.py +10 -33
  110. shotgun/tui/app.py +310 -46
  111. shotgun/tui/commands/__init__.py +1 -1
  112. shotgun/tui/components/context_indicator.py +179 -0
  113. shotgun/tui/components/mode_indicator.py +70 -0
  114. shotgun/tui/components/status_bar.py +48 -0
  115. shotgun/tui/containers.py +91 -0
  116. shotgun/tui/dependencies.py +39 -0
  117. shotgun/tui/layout.py +5 -0
  118. shotgun/tui/protocols.py +45 -0
  119. shotgun/tui/screens/chat/__init__.py +5 -0
  120. shotgun/tui/screens/chat/chat.tcss +54 -0
  121. shotgun/tui/screens/chat/chat_screen.py +1531 -0
  122. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +243 -0
  123. shotgun/tui/screens/chat/codebase_index_selection.py +12 -0
  124. shotgun/tui/screens/chat/help_text.py +40 -0
  125. shotgun/tui/screens/chat/prompt_history.py +48 -0
  126. shotgun/tui/screens/chat.tcss +11 -0
  127. shotgun/tui/screens/chat_screen/command_providers.py +91 -4
  128. shotgun/tui/screens/chat_screen/hint_message.py +76 -1
  129. shotgun/tui/screens/chat_screen/history/__init__.py +22 -0
  130. shotgun/tui/screens/chat_screen/history/agent_response.py +66 -0
  131. shotgun/tui/screens/chat_screen/history/chat_history.py +115 -0
  132. shotgun/tui/screens/chat_screen/history/formatters.py +115 -0
  133. shotgun/tui/screens/chat_screen/history/partial_response.py +43 -0
  134. shotgun/tui/screens/chat_screen/history/user_question.py +42 -0
  135. shotgun/tui/screens/confirmation_dialog.py +191 -0
  136. shotgun/tui/screens/directory_setup.py +45 -41
  137. shotgun/tui/screens/feedback.py +14 -7
  138. shotgun/tui/screens/github_issue.py +111 -0
  139. shotgun/tui/screens/model_picker.py +77 -32
  140. shotgun/tui/screens/onboarding.py +580 -0
  141. shotgun/tui/screens/pipx_migration.py +205 -0
  142. shotgun/tui/screens/provider_config.py +116 -35
  143. shotgun/tui/screens/shared_specs/__init__.py +21 -0
  144. shotgun/tui/screens/shared_specs/create_spec_dialog.py +273 -0
  145. shotgun/tui/screens/shared_specs/models.py +56 -0
  146. shotgun/tui/screens/shared_specs/share_specs_dialog.py +390 -0
  147. shotgun/tui/screens/shared_specs/upload_progress_screen.py +452 -0
  148. shotgun/tui/screens/shotgun_auth.py +112 -18
  149. shotgun/tui/screens/spec_pull.py +288 -0
  150. shotgun/tui/screens/welcome.py +137 -11
  151. shotgun/tui/services/__init__.py +5 -0
  152. shotgun/tui/services/conversation_service.py +187 -0
  153. shotgun/tui/state/__init__.py +7 -0
  154. shotgun/tui/state/processing_state.py +185 -0
  155. shotgun/tui/utils/mode_progress.py +14 -7
  156. shotgun/tui/widgets/__init__.py +5 -0
  157. shotgun/tui/widgets/widget_coordinator.py +263 -0
  158. shotgun/utils/file_system_utils.py +22 -2
  159. shotgun/utils/marketing.py +110 -0
  160. shotgun/utils/update_checker.py +69 -14
  161. shotgun_sh-0.3.3.dev1.dist-info/METADATA +472 -0
  162. shotgun_sh-0.3.3.dev1.dist-info/RECORD +229 -0
  163. {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/WHEEL +1 -1
  164. {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/entry_points.txt +1 -0
  165. {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/licenses/LICENSE +1 -1
  166. shotgun/tui/screens/chat.py +0 -996
  167. shotgun/tui/screens/chat_screen/history.py +0 -335
  168. shotgun_sh-0.2.8.dev2.dist-info/METADATA +0 -126
  169. shotgun_sh-0.2.8.dev2.dist-info/RECORD +0 -155
  170. /shotgun/agents/{history → conversation/history}/__init__.py +0 -0
  171. /shotgun/agents/{history → conversation/history}/context_extraction.py +0 -0
  172. /shotgun/agents/{history → conversation/history}/history_building.py +0 -0
  173. /shotgun/agents/{history → conversation/history}/message_utils.py +0 -0
  174. /shotgun/agents/{history → conversation/history}/token_counting/__init__.py +0 -0
  175. /shotgun/agents/{history → conversation/history}/token_estimation.py +0 -0
@@ -6,6 +6,22 @@ Transform requirements into detailed, actionable specifications that development
6
6
 
7
7
  {% include 'agents/partials/common_agent_system_prompt.j2' %}
8
8
 
9
+ ## YOUR SCOPE AND HANDOFFS
10
+
11
+ You are the **Specification agent**. Your files are `specification.md` and `.shotgun/contracts/*` - these are the ONLY files you can write to.
12
+
13
+ When your specification is complete, suggest the next step:
14
+ "I've completed the specification. Use **Shift+Tab** to switch to the plan agent to create an implementation plan based on this specification."
15
+
16
+ If the user asks you to edit other files, redirect them helpfully:
17
+ - For research.md: "I can't edit research.md - that's handled by the research agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
18
+ - For plan.md: "I can't edit plan.md - that's handled by the plan agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
19
+ - For tasks.md: "I can't edit tasks.md - that's handled by the tasks agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
20
+
21
+ NEVER offer to do work outside your scope:
22
+ - Don't offer to write research, plans, or tasks - redirect the user to the appropriate agent
23
+ - Don't offer to implement code - you are not a coding agent
24
+
9
25
  ## MEMORY MANAGEMENT PROTOCOL
10
26
 
11
27
  - You have exclusive write access to: `specification.md` and `.shotgun/contracts/*`
@@ -24,6 +40,7 @@ Transform requirements into detailed, actionable specifications that development
24
40
  specification.md is your prose documentation file. It should contain:
25
41
 
26
42
  **INCLUDE in specification.md:**
43
+ - TLDR section at the very top (key points, major features, key concerns if any)
27
44
  - Requirements and business context (what needs to be built and why)
28
45
  - Architecture overview and system design decisions
29
46
  - Component descriptions and how they interact
@@ -43,6 +60,41 @@ specification.md is your prose documentation file. It should contain:
43
60
  **When you need to show structure:** Reference contract files instead of inline code.
44
61
  Example: "User authentication uses OAuth2. See contracts/auth_types.ts for AuthUser and AuthToken types."
45
62
 
63
+ ## TLDR SECTION (REQUIRED)
64
+
65
+ Every specification.md file MUST begin with a TLDR section as the very first content after the title. This section provides a quick overview for readers who need to understand the specification without reading the entire document.
66
+
67
+ **TLDR Section Format:**
68
+
69
+ ```markdown
70
+ # Specification: [Project/Feature Name]
71
+
72
+ ## TLDR
73
+
74
+ **Key Points:**
75
+ - [Brief description of what is being built - 1-2 sentences]
76
+ - [Primary purpose/goal]
77
+
78
+ **Major Features:**
79
+ - [Feature 1 - one line]
80
+ - [Feature 2 - one line]
81
+ - [Feature 3 - one line]
82
+ - ...
83
+
84
+ **Key Concerns:** (only if applicable)
85
+ - [Concern 1 - keep brief, elaborate in relevant sections below]
86
+ - [Concern 2]
87
+ ```
88
+
89
+ **TLDR Guidelines:**
90
+ - Keep the entire TLDR section to 10-15 lines maximum
91
+ - Use bullet points for scannability
92
+ - The "Key Points" should capture the essence in 2-3 bullets
93
+ - "Major Features" lists the main capabilities (not exhaustive, just highlights)
94
+ - **"Key Concerns" is optional** - only include this subsection if there are significant risks, constraints, or decisions that readers should be aware of upfront. Omit it entirely if there are no concerns.
95
+ - Elaborate on concerns in the appropriate sections below, not in the TLDR
96
+ - The TLDR should be self-contained - someone reading only this section should understand what the project is about
97
+
46
98
  ## CONTRACT FILES
47
99
 
48
100
  Contract files define the **interfaces and types** that form contracts between components.
@@ -303,7 +355,8 @@ For specification tasks:
303
355
  2. **Check research**: Read `research.md` if it exists to understand technical context and findings
304
356
  3. **Analyze requirements**: Understand the functional and non-functional requirements
305
357
  4. **Define specifications**: Create detailed technical and functional specifications
306
- 5. **Structure documentation**: Use `write_file("specification.md", content)` to save comprehensive specifications
358
+ 5. **Write TLDR section**: Start specification.md with a TLDR section summarizing key points, major features, and any key concerns
359
+ 6. **Structure documentation**: Use `write_file("specification.md", content)` to save comprehensive specifications
307
360
 
308
361
  ## SPECIFICATION PRINCIPLES
309
362
 
@@ -22,8 +22,6 @@ No files currently exist in your allowed directories. You can create:
22
22
  - `exports/` folder - For exported documents
23
23
  {% endif %}
24
24
 
25
- When updating a file try to add into the footer that this was created using Shotgun (https://shotgun.sh).
26
-
27
25
  {% if markdown_toc %}
28
26
  ## Document Table of Contents - READ THE ENTIRE FILE TO UNDERSTAND MORE
29
27
 
@@ -4,6 +4,22 @@ Your job is to help create and manage actionable tasks for software projects and
4
4
 
5
5
  {% include 'agents/partials/common_agent_system_prompt.j2' %}
6
6
 
7
+ ## YOUR SCOPE AND HANDOFFS
8
+
9
+ You are the **Tasks agent**. Your file is `tasks.md` - this is the ONLY file you can write to.
10
+
11
+ When your tasks are complete, suggest the next step:
12
+ "I've created the task list in tasks.md. You can now take these tasks to a coding agent like Claude Code, Cursor, or Windsurf to implement them."
13
+
14
+ If the user asks you to edit other files, redirect them helpfully:
15
+ - For research.md: "I can't edit research.md - that's handled by the research agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
16
+ - For specification.md or contracts: "I can't edit specification.md - that's handled by the specification agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
17
+ - For plan.md: "I can't edit plan.md - that's handled by the plan agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
18
+
19
+ NEVER offer to do work outside your scope:
20
+ - Don't offer to write research, specifications, or plans - redirect the user to the appropriate agent
21
+ - Don't offer to implement code - you are not a coding agent
22
+
7
23
  ## MEMORY MANAGEMENT PROTOCOL
8
24
 
9
25
  - You have exclusive write access to: `tasks.md`
@@ -0,0 +1,34 @@
1
+ You are summarizing chunk {{ chunk_index }} of {{ total_chunks }} from a conversation that is too large to summarize at once.
2
+
3
+ Focus on preserving:
4
+ 1. ALL file paths, function names, variable names, URLs, and identifiers VERBATIM
5
+ 2. Key decisions and their rationale
6
+ 3. Tool call results (summarize large outputs, keep critical data)
7
+ 4. Important errors or issues encountered
8
+ 5. User requests and how they were addressed
9
+
10
+ <CHUNK_CONTENT>
11
+ {{ chunk_content }}
12
+ </CHUNK_CONTENT>
13
+
14
+ <OUTPUT_FORMAT>
15
+ ## Topics Covered
16
+ Brief bullet points of main topics discussed in this chunk.
17
+
18
+ ## Entities & References
19
+ - Files: [list all file paths mentioned, verbatim]
20
+ - Functions/Classes: [list all function/method/class names, verbatim]
21
+ - Variables/Keys: [configuration keys, env vars, etc., verbatim]
22
+ - Links/IDs: [any URLs, ticket IDs, etc., verbatim]
23
+
24
+ ## Actions & Results
25
+ Summarized tool calls and their outcomes. Preserve important data verbatim.
26
+
27
+ ## Important Decisions
28
+ Key decisions made and their rationale.
29
+
30
+ ## Status at Chunk End
31
+ Current active task or objective at the end of this chunk (if any), or "N/A" if this chunk completes without active work.
32
+ </OUTPUT_FORMAT>
33
+
34
+ Be concise but preserve all critical information. This summary will be combined with others to create a complete conversation summary.
@@ -0,0 +1,53 @@
1
+ You are combining {{ num_summaries }} conversation chunk summaries into a unified summary.
2
+
3
+ <CHUNK_SUMMARIES>
4
+ {% for summary in chunk_summaries %}
5
+ --- Chunk {{ loop.index }} of {{ num_summaries }} ---
6
+ {{ summary }}
7
+
8
+ {% endfor %}
9
+ </CHUNK_SUMMARIES>
10
+
11
+ Instructions:
12
+ 1. Merge overlapping information - don't repeat the same facts
13
+ 2. Preserve ALL entity references (files, functions, variables) from ALL chunks
14
+ 3. Maintain chronological timeline of actions
15
+ 4. Identify patterns and evolution across chunks
16
+ 5. The current status should reflect the LAST chunk's end state
17
+
18
+ <OUTPUT_FORMAT>
19
+ # Context
20
+
21
+ Short summary of the discussion, grouped by topics or tasks if any.
22
+ Present it as clear bullet points. Be brief but do not lose information that might be important for the current state, task or objectives.
23
+
24
+ # Key elements, learnings and entities
25
+
26
+ Present the important elements of context, learnings and entities from the discussion. Be very detailed.
27
+
28
+ Present it as clear bullet points. Be brief but do not lose information that might be important for the current state, task or objectives.
29
+
30
+ If the agent obtained information via tools, preserve it verbatim if it was short, summarize it if it was long focusing on preserving all of the most important facts and information.
31
+
32
+ If there are any links mentioned, IDs, filenames, etc. - preserve them verbatim.
33
+
34
+ # Timeline
35
+
36
+ For important tasks and content, provide
37
+ - User: asked to handle a complex task X: <summary of the task>
38
+ - Assistant: we handle it by doing A, B, C...
39
+ - Tools used and output summary
40
+
41
+ ...
42
+ - User: asked to handle a complex task Y: <summary of the task>
43
+ - Assistant: we handle it by doing A, B, C...
44
+ - Tools used and output summary
45
+
46
+ Do not hesitate to merge original messages, remove noise. We want to be as concise and brief as possible.
47
+
48
+ # Current status, task and objectives
49
+
50
+ Based on the conversation, include here the current status, task and objectives.
51
+
52
+ CRITICAL: This is not about summarizing or compacting. We are talking about the status, task and objectives currently active in the conversation to keep as much context as possible regarding the last messages.
53
+ </OUTPUT_FORMAT>
shotgun/sdk/codebase.py CHANGED
@@ -93,6 +93,19 @@ class CodebaseSDK:
93
93
  if indexed_from_cwd is None:
94
94
  indexed_from_cwd = str(Path.cwd().resolve())
95
95
 
96
+ # Track codebase indexing started event
97
+ source = detect_source()
98
+ logger.debug(
99
+ "Tracking codebase_index_started event: source=%s",
100
+ source,
101
+ )
102
+ track_event(
103
+ "codebase_index_started",
104
+ {
105
+ "source": source,
106
+ },
107
+ )
108
+
96
109
  graph = await self.service.create_graph(
97
110
  resolved_path,
98
111
  name,
@@ -101,9 +114,7 @@ class CodebaseSDK:
101
114
  )
102
115
  file_count = sum(graph.language_stats.values()) if graph.language_stats else 0
103
116
 
104
- # Track codebase indexing event
105
- # Detect if called from TUI by checking the call stack
106
- source = detect_source()
117
+ # Track codebase indexing completion event (reuse source from start event)
107
118
 
108
119
  logger.debug(
109
120
  "Tracking codebase_indexed event: file_count=%d, node_count=%d, relationship_count=%d, source=%s",
@@ -1,14 +1,132 @@
1
1
  """Sentry observability setup for Shotgun."""
2
2
 
3
- import os
3
+ from pathlib import Path
4
+ from typing import Any
4
5
 
5
6
  from shotgun import __version__
6
7
  from shotgun.logging_config import get_early_logger
8
+ from shotgun.settings import settings
7
9
 
8
10
  # Use early logger to prevent automatic StreamHandler creation
9
11
  logger = get_early_logger(__name__)
10
12
 
11
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
+
12
130
  def setup_sentry_observability() -> bool:
13
131
  """Set up Sentry observability for error tracking.
14
132
 
@@ -23,48 +141,77 @@ def setup_sentry_observability() -> bool:
23
141
  logger.debug("Sentry is already initialized, skipping")
24
142
  return True
25
143
 
26
- # Try to get DSN from build constants first (production builds)
27
- dsn = None
28
- try:
29
- from shotgun import build_constants
30
-
31
- dsn = build_constants.SENTRY_DSN
32
- logger.debug("Using Sentry DSN from build constants")
33
- except ImportError:
34
- # Fallback to environment variable (development)
35
- dsn = os.getenv("SENTRY_DSN", "")
36
- if dsn:
37
- logger.debug("Using Sentry DSN from environment variable")
144
+ # Get DSN from settings (handles build constants + env vars automatically)
145
+ dsn = settings.telemetry.sentry_dsn
38
146
 
39
147
  if not dsn:
40
148
  logger.debug("No Sentry DSN configured, skipping Sentry initialization")
41
149
  return False
42
150
 
43
- logger.debug("Found DSN, proceeding with Sentry setup")
151
+ logger.debug("Using Sentry DSN from settings, proceeding with setup")
44
152
 
45
153
  # Determine environment based on version
46
- # Dev versions contain "dev", "rc", "alpha", or "beta"
154
+ # Dev versions contain "dev", "rc", "alpha", "beta"
47
155
  if any(marker in __version__ for marker in ["dev", "rc", "alpha", "beta"]):
48
156
  environment = "development"
49
157
  else:
50
158
  environment = "production"
51
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
+
52
195
  # Initialize Sentry
53
196
  sentry_sdk.init(
54
197
  dsn=dsn,
55
198
  release=f"shotgun-sh@{__version__}",
56
199
  environment=environment,
57
200
  send_default_pii=False, # Privacy-first: never send PII
201
+ server_name="", # Privacy: don't send hostname (may contain username)
58
202
  traces_sample_rate=0.1 if environment == "production" else 1.0,
59
203
  profiles_sample_rate=0.1 if environment == "production" else 1.0,
204
+ before_send=before_send,
60
205
  )
61
206
 
62
207
  # Set user context with anonymous shotgun instance ID from config
63
208
  try:
209
+ import asyncio
210
+
64
211
  from shotgun.agents.config import get_config_manager
65
212
 
66
213
  config_manager = get_config_manager()
67
- shotgun_instance_id = config_manager.get_shotgun_instance_id()
214
+ shotgun_instance_id = asyncio.run(config_manager.get_shotgun_instance_id())
68
215
  sentry_sdk.set_user({"id": shotgun_instance_id})
69
216
  logger.debug("Sentry user context set with anonymous ID")
70
217
  except Exception as e: