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.
- shotgun/agents/agent_manager.py +382 -60
- shotgun/agents/common.py +15 -9
- shotgun/agents/config/README.md +89 -0
- shotgun/agents/config/__init__.py +10 -1
- shotgun/agents/config/constants.py +0 -6
- shotgun/agents/config/manager.py +383 -82
- shotgun/agents/config/models.py +122 -18
- shotgun/agents/config/provider.py +81 -15
- shotgun/agents/config/streaming_test.py +119 -0
- shotgun/agents/context_analyzer/__init__.py +28 -0
- shotgun/agents/context_analyzer/analyzer.py +475 -0
- shotgun/agents/context_analyzer/constants.py +9 -0
- shotgun/agents/context_analyzer/formatter.py +115 -0
- shotgun/agents/context_analyzer/models.py +212 -0
- shotgun/agents/conversation/__init__.py +18 -0
- shotgun/agents/conversation/filters.py +164 -0
- shotgun/agents/conversation/history/chunking.py +278 -0
- shotgun/agents/{history → conversation/history}/compaction.py +36 -5
- shotgun/agents/{history → conversation/history}/constants.py +5 -0
- shotgun/agents/conversation/history/file_content_deduplication.py +216 -0
- shotgun/agents/{history → conversation/history}/history_processors.py +380 -8
- shotgun/agents/{history → conversation/history}/token_counting/anthropic.py +25 -1
- shotgun/agents/{history → conversation/history}/token_counting/base.py +14 -3
- shotgun/agents/{history → conversation/history}/token_counting/openai.py +11 -1
- shotgun/agents/{history → conversation/history}/token_counting/sentencepiece_counter.py +8 -0
- shotgun/agents/{history → conversation/history}/token_counting/tokenizer_cache.py +3 -1
- shotgun/agents/{history → conversation/history}/token_counting/utils.py +0 -3
- shotgun/agents/{conversation_manager.py → conversation/manager.py} +36 -20
- shotgun/agents/{conversation_history.py → conversation/models.py} +8 -92
- shotgun/agents/error/__init__.py +11 -0
- shotgun/agents/error/models.py +19 -0
- shotgun/agents/export.py +2 -2
- shotgun/agents/plan.py +2 -2
- shotgun/agents/research.py +3 -3
- shotgun/agents/runner.py +230 -0
- shotgun/agents/specify.py +2 -2
- shotgun/agents/tasks.py +2 -2
- shotgun/agents/tools/codebase/codebase_shell.py +6 -0
- shotgun/agents/tools/codebase/directory_lister.py +6 -0
- shotgun/agents/tools/codebase/file_read.py +11 -2
- shotgun/agents/tools/codebase/query_graph.py +6 -0
- shotgun/agents/tools/codebase/retrieve_code.py +6 -0
- shotgun/agents/tools/file_management.py +27 -7
- shotgun/agents/tools/registry.py +217 -0
- shotgun/agents/tools/web_search/__init__.py +8 -8
- shotgun/agents/tools/web_search/anthropic.py +8 -2
- shotgun/agents/tools/web_search/gemini.py +7 -1
- shotgun/agents/tools/web_search/openai.py +8 -2
- shotgun/agents/tools/web_search/utils.py +2 -2
- shotgun/agents/usage_manager.py +16 -11
- shotgun/api_endpoints.py +7 -3
- shotgun/build_constants.py +2 -2
- shotgun/cli/clear.py +53 -0
- shotgun/cli/compact.py +188 -0
- shotgun/cli/config.py +8 -5
- shotgun/cli/context.py +154 -0
- shotgun/cli/error_handler.py +24 -0
- shotgun/cli/export.py +34 -34
- shotgun/cli/feedback.py +4 -2
- shotgun/cli/models.py +1 -0
- shotgun/cli/plan.py +34 -34
- shotgun/cli/research.py +18 -10
- shotgun/cli/spec/__init__.py +5 -0
- shotgun/cli/spec/backup.py +81 -0
- shotgun/cli/spec/commands.py +132 -0
- shotgun/cli/spec/models.py +48 -0
- shotgun/cli/spec/pull_service.py +219 -0
- shotgun/cli/specify.py +20 -19
- shotgun/cli/tasks.py +34 -34
- shotgun/cli/update.py +16 -2
- shotgun/codebase/core/change_detector.py +5 -3
- shotgun/codebase/core/code_retrieval.py +4 -2
- shotgun/codebase/core/ingestor.py +163 -15
- shotgun/codebase/core/manager.py +13 -4
- shotgun/codebase/core/nl_query.py +1 -1
- shotgun/codebase/models.py +2 -0
- shotgun/exceptions.py +357 -0
- shotgun/llm_proxy/__init__.py +17 -0
- shotgun/llm_proxy/client.py +215 -0
- shotgun/llm_proxy/models.py +137 -0
- shotgun/logging_config.py +60 -27
- shotgun/main.py +77 -11
- shotgun/posthog_telemetry.py +38 -29
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +28 -2
- shotgun/prompts/agents/partials/interactive_mode.j2 +3 -3
- shotgun/prompts/agents/plan.j2 +16 -0
- shotgun/prompts/agents/research.j2 +16 -3
- shotgun/prompts/agents/specify.j2 +54 -1
- shotgun/prompts/agents/state/system_state.j2 +0 -2
- shotgun/prompts/agents/tasks.j2 +16 -0
- shotgun/prompts/history/chunk_summarization.j2 +34 -0
- shotgun/prompts/history/combine_summaries.j2 +53 -0
- shotgun/sdk/codebase.py +14 -3
- shotgun/sentry_telemetry.py +163 -16
- shotgun/settings.py +243 -0
- shotgun/shotgun_web/__init__.py +67 -1
- shotgun/shotgun_web/client.py +42 -1
- shotgun/shotgun_web/constants.py +46 -0
- shotgun/shotgun_web/exceptions.py +29 -0
- shotgun/shotgun_web/models.py +390 -0
- shotgun/shotgun_web/shared_specs/__init__.py +32 -0
- shotgun/shotgun_web/shared_specs/file_scanner.py +175 -0
- shotgun/shotgun_web/shared_specs/hasher.py +83 -0
- shotgun/shotgun_web/shared_specs/models.py +71 -0
- shotgun/shotgun_web/shared_specs/upload_pipeline.py +329 -0
- shotgun/shotgun_web/shared_specs/utils.py +34 -0
- shotgun/shotgun_web/specs_client.py +703 -0
- shotgun/shotgun_web/supabase_client.py +31 -0
- shotgun/telemetry.py +10 -33
- shotgun/tui/app.py +310 -46
- shotgun/tui/commands/__init__.py +1 -1
- shotgun/tui/components/context_indicator.py +179 -0
- shotgun/tui/components/mode_indicator.py +70 -0
- shotgun/tui/components/status_bar.py +48 -0
- shotgun/tui/containers.py +91 -0
- shotgun/tui/dependencies.py +39 -0
- shotgun/tui/layout.py +5 -0
- shotgun/tui/protocols.py +45 -0
- shotgun/tui/screens/chat/__init__.py +5 -0
- shotgun/tui/screens/chat/chat.tcss +54 -0
- shotgun/tui/screens/chat/chat_screen.py +1531 -0
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +243 -0
- shotgun/tui/screens/chat/codebase_index_selection.py +12 -0
- shotgun/tui/screens/chat/help_text.py +40 -0
- shotgun/tui/screens/chat/prompt_history.py +48 -0
- shotgun/tui/screens/chat.tcss +11 -0
- shotgun/tui/screens/chat_screen/command_providers.py +91 -4
- shotgun/tui/screens/chat_screen/hint_message.py +76 -1
- shotgun/tui/screens/chat_screen/history/__init__.py +22 -0
- shotgun/tui/screens/chat_screen/history/agent_response.py +66 -0
- shotgun/tui/screens/chat_screen/history/chat_history.py +115 -0
- shotgun/tui/screens/chat_screen/history/formatters.py +115 -0
- shotgun/tui/screens/chat_screen/history/partial_response.py +43 -0
- shotgun/tui/screens/chat_screen/history/user_question.py +42 -0
- shotgun/tui/screens/confirmation_dialog.py +191 -0
- shotgun/tui/screens/directory_setup.py +45 -41
- shotgun/tui/screens/feedback.py +14 -7
- shotgun/tui/screens/github_issue.py +111 -0
- shotgun/tui/screens/model_picker.py +77 -32
- shotgun/tui/screens/onboarding.py +580 -0
- shotgun/tui/screens/pipx_migration.py +205 -0
- shotgun/tui/screens/provider_config.py +116 -35
- shotgun/tui/screens/shared_specs/__init__.py +21 -0
- shotgun/tui/screens/shared_specs/create_spec_dialog.py +273 -0
- shotgun/tui/screens/shared_specs/models.py +56 -0
- shotgun/tui/screens/shared_specs/share_specs_dialog.py +390 -0
- shotgun/tui/screens/shared_specs/upload_progress_screen.py +452 -0
- shotgun/tui/screens/shotgun_auth.py +112 -18
- shotgun/tui/screens/spec_pull.py +288 -0
- shotgun/tui/screens/welcome.py +137 -11
- shotgun/tui/services/__init__.py +5 -0
- shotgun/tui/services/conversation_service.py +187 -0
- shotgun/tui/state/__init__.py +7 -0
- shotgun/tui/state/processing_state.py +185 -0
- shotgun/tui/utils/mode_progress.py +14 -7
- shotgun/tui/widgets/__init__.py +5 -0
- shotgun/tui/widgets/widget_coordinator.py +263 -0
- shotgun/utils/file_system_utils.py +22 -2
- shotgun/utils/marketing.py +110 -0
- shotgun/utils/update_checker.py +69 -14
- shotgun_sh-0.3.3.dev1.dist-info/METADATA +472 -0
- shotgun_sh-0.3.3.dev1.dist-info/RECORD +229 -0
- {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/WHEEL +1 -1
- {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/entry_points.txt +1 -0
- {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/licenses/LICENSE +1 -1
- shotgun/tui/screens/chat.py +0 -996
- shotgun/tui/screens/chat_screen/history.py +0 -335
- shotgun_sh-0.2.8.dev2.dist-info/METADATA +0 -126
- shotgun_sh-0.2.8.dev2.dist-info/RECORD +0 -155
- /shotgun/agents/{history → conversation/history}/__init__.py +0 -0
- /shotgun/agents/{history → conversation/history}/context_extraction.py +0 -0
- /shotgun/agents/{history → conversation/history}/history_building.py +0 -0
- /shotgun/agents/{history → conversation/history}/message_utils.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/__init__.py +0 -0
- /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. **
|
|
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
|
|
shotgun/prompts/agents/tasks.j2
CHANGED
|
@@ -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",
|
shotgun/sentry_telemetry.py
CHANGED
|
@@ -1,14 +1,132 @@
|
|
|
1
1
|
"""Sentry observability setup for Shotgun."""
|
|
2
2
|
|
|
3
|
-
import
|
|
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
|
-
#
|
|
27
|
-
dsn =
|
|
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("
|
|
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",
|
|
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:
|