shotgun-sh 0.2.3.dev2__py3-none-any.whl → 0.2.11.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of shotgun-sh might be problematic. Click here for more details.

Files changed (107) hide show
  1. shotgun/agents/agent_manager.py +524 -58
  2. shotgun/agents/common.py +62 -62
  3. shotgun/agents/config/constants.py +0 -6
  4. shotgun/agents/config/manager.py +14 -3
  5. shotgun/agents/config/models.py +16 -0
  6. shotgun/agents/config/provider.py +68 -13
  7. shotgun/agents/context_analyzer/__init__.py +28 -0
  8. shotgun/agents/context_analyzer/analyzer.py +493 -0
  9. shotgun/agents/context_analyzer/constants.py +9 -0
  10. shotgun/agents/context_analyzer/formatter.py +115 -0
  11. shotgun/agents/context_analyzer/models.py +212 -0
  12. shotgun/agents/conversation_history.py +125 -2
  13. shotgun/agents/conversation_manager.py +24 -2
  14. shotgun/agents/export.py +4 -5
  15. shotgun/agents/history/compaction.py +9 -4
  16. shotgun/agents/history/context_extraction.py +93 -6
  17. shotgun/agents/history/history_processors.py +14 -2
  18. shotgun/agents/history/token_counting/anthropic.py +32 -10
  19. shotgun/agents/models.py +50 -2
  20. shotgun/agents/plan.py +4 -5
  21. shotgun/agents/research.py +4 -5
  22. shotgun/agents/specify.py +4 -5
  23. shotgun/agents/tasks.py +4 -5
  24. shotgun/agents/tools/__init__.py +0 -2
  25. shotgun/agents/tools/codebase/codebase_shell.py +6 -0
  26. shotgun/agents/tools/codebase/directory_lister.py +6 -0
  27. shotgun/agents/tools/codebase/file_read.py +6 -0
  28. shotgun/agents/tools/codebase/query_graph.py +6 -0
  29. shotgun/agents/tools/codebase/retrieve_code.py +6 -0
  30. shotgun/agents/tools/file_management.py +71 -9
  31. shotgun/agents/tools/registry.py +217 -0
  32. shotgun/agents/tools/web_search/__init__.py +24 -12
  33. shotgun/agents/tools/web_search/anthropic.py +24 -3
  34. shotgun/agents/tools/web_search/gemini.py +22 -10
  35. shotgun/agents/tools/web_search/openai.py +21 -12
  36. shotgun/api_endpoints.py +7 -3
  37. shotgun/build_constants.py +1 -1
  38. shotgun/cli/clear.py +52 -0
  39. shotgun/cli/compact.py +186 -0
  40. shotgun/cli/context.py +111 -0
  41. shotgun/cli/models.py +1 -0
  42. shotgun/cli/update.py +16 -2
  43. shotgun/codebase/core/manager.py +10 -1
  44. shotgun/llm_proxy/__init__.py +5 -2
  45. shotgun/llm_proxy/clients.py +12 -7
  46. shotgun/logging_config.py +8 -10
  47. shotgun/main.py +70 -10
  48. shotgun/posthog_telemetry.py +9 -3
  49. shotgun/prompts/agents/export.j2 +18 -1
  50. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +5 -1
  51. shotgun/prompts/agents/partials/interactive_mode.j2 +24 -7
  52. shotgun/prompts/agents/plan.j2 +1 -1
  53. shotgun/prompts/agents/research.j2 +1 -1
  54. shotgun/prompts/agents/specify.j2 +270 -3
  55. shotgun/prompts/agents/state/system_state.j2 +4 -0
  56. shotgun/prompts/agents/tasks.j2 +1 -1
  57. shotgun/prompts/loader.py +2 -2
  58. shotgun/prompts/tools/web_search.j2 +14 -0
  59. shotgun/sentry_telemetry.py +4 -15
  60. shotgun/settings.py +238 -0
  61. shotgun/telemetry.py +15 -32
  62. shotgun/tui/app.py +203 -9
  63. shotgun/tui/commands/__init__.py +1 -1
  64. shotgun/tui/components/context_indicator.py +136 -0
  65. shotgun/tui/components/mode_indicator.py +70 -0
  66. shotgun/tui/components/status_bar.py +48 -0
  67. shotgun/tui/containers.py +93 -0
  68. shotgun/tui/dependencies.py +39 -0
  69. shotgun/tui/protocols.py +45 -0
  70. shotgun/tui/screens/chat/__init__.py +5 -0
  71. shotgun/tui/screens/chat/chat.tcss +54 -0
  72. shotgun/tui/screens/chat/chat_screen.py +1110 -0
  73. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +64 -0
  74. shotgun/tui/screens/chat/codebase_index_selection.py +12 -0
  75. shotgun/tui/screens/chat/help_text.py +39 -0
  76. shotgun/tui/screens/chat/prompt_history.py +48 -0
  77. shotgun/tui/screens/chat.tcss +11 -0
  78. shotgun/tui/screens/chat_screen/command_providers.py +68 -2
  79. shotgun/tui/screens/chat_screen/history/__init__.py +22 -0
  80. shotgun/tui/screens/chat_screen/history/agent_response.py +66 -0
  81. shotgun/tui/screens/chat_screen/history/chat_history.py +116 -0
  82. shotgun/tui/screens/chat_screen/history/formatters.py +115 -0
  83. shotgun/tui/screens/chat_screen/history/partial_response.py +43 -0
  84. shotgun/tui/screens/chat_screen/history/user_question.py +42 -0
  85. shotgun/tui/screens/confirmation_dialog.py +151 -0
  86. shotgun/tui/screens/model_picker.py +30 -6
  87. shotgun/tui/screens/pipx_migration.py +153 -0
  88. shotgun/tui/screens/welcome.py +24 -5
  89. shotgun/tui/services/__init__.py +5 -0
  90. shotgun/tui/services/conversation_service.py +182 -0
  91. shotgun/tui/state/__init__.py +7 -0
  92. shotgun/tui/state/processing_state.py +185 -0
  93. shotgun/tui/widgets/__init__.py +5 -0
  94. shotgun/tui/widgets/widget_coordinator.py +247 -0
  95. shotgun/utils/datetime_utils.py +77 -0
  96. shotgun/utils/file_system_utils.py +3 -2
  97. shotgun/utils/update_checker.py +69 -14
  98. shotgun_sh-0.2.11.dev1.dist-info/METADATA +129 -0
  99. shotgun_sh-0.2.11.dev1.dist-info/RECORD +190 -0
  100. {shotgun_sh-0.2.3.dev2.dist-info → shotgun_sh-0.2.11.dev1.dist-info}/entry_points.txt +1 -0
  101. {shotgun_sh-0.2.3.dev2.dist-info → shotgun_sh-0.2.11.dev1.dist-info}/licenses/LICENSE +1 -1
  102. shotgun/agents/tools/user_interaction.py +0 -37
  103. shotgun/tui/screens/chat.py +0 -804
  104. shotgun/tui/screens/chat_screen/history.py +0 -352
  105. shotgun_sh-0.2.3.dev2.dist-info/METADATA +0 -467
  106. shotgun_sh-0.2.3.dev2.dist-info/RECORD +0 -154
  107. {shotgun_sh-0.2.3.dev2.dist-info → shotgun_sh-0.2.11.dev1.dist-info}/WHEEL +0 -0
@@ -6,20 +6,37 @@
6
6
  {% if interactive_mode -%}
7
7
  IMPORTANT: USER INTERACTION IS ENABLED (interactive mode).
8
8
 
9
- - BEFORE GETTING TO WORK, ALWAYS THINK WHAT YOU'RE GOING TO DO AND ask_user() TO ACCEPT WHAT YOU'RE GOING TO DO.
10
- - ALWAYS USE ask_user TO REVIEW AND ACCEPT THE ARTIFACT SECTION YOU'VE WORKED ON BEFORE PROCEEDING TO THE NEXT SECTION.
11
- - AFTER USING write_artifact_section(), ALWAYS USE ask_user() TO REVIEW AND ACCEPT THE ARTIFACT SECTION YOU'VE WORKED ON BEFORE PROCEEDING TO THE NEXT SECTION.
9
+ ## Structured Output Format
10
+
11
+ You must return responses using this structured format:
12
+
13
+ ```json
14
+ {
15
+ "response": "Your main response text here",
16
+ "clarifying_questions": ["Question 1?", "Question 2?"] // Optional, only when needed
17
+ }
18
+ ```
19
+
20
+ ## When to Use Clarifying Questions
21
+
22
+ - BEFORE GETTING TO WORK: If the user's request is ambiguous, use clarifying_questions to ask what they want
23
+ - DURING WORK: After using write_file(), you can suggest that the user review it and ask any clarifying questions with clarifying_questions
12
24
  - Don't assume - ask for confirmation of your understanding
13
- - When in doubt about any aspect of the goal, ASK before proceeding
25
+ - When in doubt about any aspect of the goal, include clarifying_questions
26
+
27
+ ## Important Notes
28
+
29
+ - If you don't need to ask questions, set clarifying_questions to null or omit it
30
+ - Keep response field concise - a paragraph at most for user communication
31
+ - Questions should be clear, specific, and independently answerable
32
+ - Don't ask multiple questions in one string - use separate array items
14
33
 
15
34
  {% else -%}
16
35
 
17
36
  IMPORTANT: USER INTERACTION IS DISABLED (non-interactive mode).
18
- - You cannot ask clarifying questions using ask_user tool
37
+ - You cannot ask clarifying questions (clarifying_questions will be ignored)
19
38
  - Make reasonable assumptions based on best practices
20
39
  - Use sensible defaults when information is missing
21
- - Make reasonable assumptions based on industry best practices
22
- - Use sensible defaults when specific details are not provided
23
40
  - When in doubt, make reasonable assumptions and proceed with best practices
24
41
  {% endif %}
25
42
 
@@ -118,7 +118,7 @@ USER INTERACTION - REDUCE UNCERTAINTY:
118
118
  - FIRST read `research.md` and `specification.md` before asking ANY questions
119
119
  - ONLY ask clarifying questions AFTER reading the context files
120
120
  - Questions should be about gaps not covered in research/specification
121
- - Use ask_user tool to gather specific details about:
121
+ - Use clarifying questions to gather specific details about:
122
122
  - Information not found in the context files
123
123
  - Clarifications on ambiguous specifications
124
124
  - Priorities when multiple options exist
@@ -39,7 +39,7 @@ For research tasks:
39
39
  ## RESEARCH PRINCIPLES
40
40
 
41
41
  {% if interactive_mode -%}
42
- - CRITICAL: BEFORE RUNNING ANY SEARCH TOOL, ASK THE USER FOR APPROVAL USING ask_user(). FINISH THE QUESTION WITH ASKING FOR A GO AHEAD.
42
+ - CRITICAL: BEFORE RUNNING ANY SEARCH TOOL, ASK THE USER FOR APPROVAL using clarifying questions. Include what you plan to search for and ask if they want you to proceed.
43
43
  {% endif -%}
44
44
  - Build upon existing research rather than starting from scratch
45
45
  - Focus on practical, actionable information over theoretical concepts
@@ -8,14 +8,281 @@ Transform requirements into detailed, actionable specifications that development
8
8
 
9
9
  ## MEMORY MANAGEMENT PROTOCOL
10
10
 
11
- - You have exclusive write access to: `specification.md`
11
+ - You have exclusive write access to: `specification.md` and `.shotgun/contracts/*`
12
12
  - SHOULD READ `research.md` for context but CANNOT write to it
13
- - This is your persistent memory store - ALWAYS load it first
13
+ - **specification.md is for PROSE ONLY** - no code, no implementation details, no type definitions
14
+ - **All code goes in .shotgun/contracts/** - types, interfaces, schemas
15
+ - specification.md describes WHAT and WHY, contracts/ show HOW with actual code
16
+ - This is your persistent memory store - ALWAYS load specification.md first
14
17
  - Compress content regularly to stay within context limits
15
- - Keep your file updated as you work - it's your memory across sessions
18
+ - Keep your files updated as you work - they're your memory across sessions
16
19
  - When adding new specifications, review and consolidate overlapping requirements
17
20
  - Structure specifications for easy reference by the next agents
18
21
 
22
+ ## WHAT GOES IN SPECIFICATION.MD
23
+
24
+ specification.md is your prose documentation file. It should contain:
25
+
26
+ **INCLUDE in specification.md:**
27
+ - Requirements and business context (what needs to be built and why)
28
+ - Architecture overview and system design decisions
29
+ - Component descriptions and how they interact
30
+ - User workflows and use cases
31
+ - Directory structure as succinct prose (e.g., "src/ contains main code, tests/ contains test files")
32
+ - Dependencies listed in prose (e.g., "Requires TypeScript 5.0+, React 18, and PostgreSQL")
33
+ - Configuration requirements described (e.g., "App needs database URL and API key in environment")
34
+ - Testing strategies and acceptance criteria
35
+ - References to contract files (e.g., "See contracts/user_models.py for User type definition")
36
+
37
+ **DO NOT INCLUDE in specification.md:**
38
+ - Code blocks, type definitions, or function signatures (those go in contracts/)
39
+ - Implementation details or algorithms (describe behavior instead)
40
+ - Actual configuration files or build manifests (describe what's needed instead)
41
+ - Directory trees or file listings (keep structure descriptions succinct)
42
+
43
+ **When you need to show structure:** Reference contract files instead of inline code.
44
+ Example: "User authentication uses OAuth2. See contracts/auth_types.ts for AuthUser and AuthToken types."
45
+
46
+ ## CONTRACT FILES
47
+
48
+ Contract files define the **interfaces and types** that form contracts between components.
49
+ They contain actual code that shows structure, not prose descriptions.
50
+
51
+ **ONLY put these in `.shotgun/contracts/` (language-agnostic):**
52
+ - **Type definitions ONLY** - Shape and structure, NO behavior or logic:
53
+ - Python: Pydantic models, dataclasses, `typing.Protocol` classes (interface definitions)
54
+ - TypeScript: interfaces, type aliases
55
+ - Rust: struct definitions
56
+ - Java: interfaces, POJOs
57
+ - C++: header files with class/struct declarations
58
+ - Go: interface types, struct definitions
59
+ - **Schema definitions**: API contracts and data schemas
60
+ - OpenAPI/Swagger specs (openapi.json, openapi.yaml)
61
+ - JSON Schema definitions
62
+ - GraphQL schemas
63
+ - Protobuf definitions
64
+ - **Protocol/Interface classes**: Pure interface definitions with method signatures only
65
+ - Python: `class Storage(Protocol): def save(self, data: str) -> None: ...`
66
+ - Use `...` (Ellipsis) for protocol methods, NOT `pass`
67
+
68
+ **NEVER put these in `.shotgun/contracts/` - NO EXECUTABLE CODE:**
69
+ - ❌ **Functions or methods with implementations** (even with `pass` or empty bodies)
70
+ - ❌ **Helper functions** with any logic whatsoever
71
+ - ❌ **Classes with method implementations** (use Protocol classes instead)
72
+ - ❌ **Standalone functions** like `def main(): pass` or `def validate_input(x): ...`
73
+ - ❌ **Code with behavior**: loops, conditionals, data manipulation, computations
74
+ - ❌ **Data constants**: dictionaries, lists, or any runtime values
75
+ - ❌ **`if __name__ == "__main__":` blocks** or any executable code
76
+ - Build/dependency configs (pyproject.toml, package.json, Cargo.toml, requirements.txt)
77
+ - Directory structure files (directory_structure.txt)
78
+ - Configuration templates (.env, config.yaml, example configs)
79
+ - Documentation or markdown files
80
+ - SQL migration files or database dumps
81
+
82
+ **These belong in specification.md instead:**
83
+ - Directory structure (as succinct prose: "src/ contains modules, tests/ has unit tests")
84
+ - Dependencies (as prose: "Requires Rust 1.70+, tokio, serde")
85
+ - Configuration needs (describe: "App needs DB_URL and API_KEY environment variables")
86
+
87
+ **Guidelines for contract files:**
88
+ - Keep each file focused on a single domain (e.g., user_types.ts, payment_models.py)
89
+ - Reference from specification.md: "See contracts/user_types.ts for User and Profile types"
90
+ - Use descriptive filenames: `auth_models.py`, `api_spec.json`, `database_types.rs`
91
+ - Keep files under 500 lines to avoid truncation
92
+ - When contracts grow large, split into focused files
93
+
94
+ **Example workflow:**
95
+ 1. In specification.md: "Authentication system with JWT tokens. See contracts/auth_types.ts for types."
96
+ 2. Create contract file: `write_file("contracts/auth_types.ts", content)` with actual TypeScript interfaces
97
+ 3. Create contract file: `write_file("contracts/auth_api.json", content)` with actual OpenAPI spec
98
+ 4. Coding agents can directly use these contracts to implement features
99
+
100
+ ## HOW TO WRITE CONTRACT FILES
101
+
102
+ **CRITICAL - Always use correct file paths with write_file():**
103
+
104
+ Your working directory is `.shotgun/`, so paths should be relative to that directory.
105
+
106
+ <GOOD_EXAMPLES>
107
+ ✅ `write_file("contracts/user_models.py", content)` - Correct path for Python models
108
+ ✅ `write_file("contracts/auth_types.ts", content)` - Correct path for TypeScript types
109
+ ✅ `write_file("contracts/api_spec.json", content)` - Correct path for OpenAPI spec
110
+ ✅ `write_file("contracts/payment_service.rs", content)` - Correct path for Rust code
111
+ </GOOD_EXAMPLES>
112
+
113
+ <BAD_EXAMPLES>
114
+ ❌ `write_file(".shotgun/contracts/user_models.py", content)` - WRONG! Don't include .shotgun/ prefix
115
+ ❌ `write_file("contracts/directory_structure.txt", content)` - WRONG! No documentation files
116
+ ❌ `write_file("contracts/pyproject.toml", content)` - WRONG! No build configs in contracts/
117
+ ❌ `write_file("contracts/requirements.txt", content)` - WRONG! No dependency lists in contracts/
118
+ ❌ `write_file("contracts/config.yaml", content)` - WRONG! No config templates in contracts/
119
+ </BAD_EXAMPLES>
120
+
121
+ **Path format rule:** Always use `contracts/filename.ext`, never `.shotgun/contracts/filename.ext`
122
+
123
+ **Language-specific examples:**
124
+
125
+ <PYTHON_EXAMPLE>
126
+ # Python Pydantic model contract
127
+ from pydantic import BaseModel, Field
128
+ from typing import Optional
129
+
130
+ class User(BaseModel):
131
+ """User model contract."""
132
+ id: int
133
+ email: str = Field(..., description="User email address")
134
+ username: str
135
+ is_active: bool = True
136
+ role: Optional[str] = None
137
+
138
+ # Save as: write_file("contracts/user_models.py", content)
139
+ </PYTHON_EXAMPLE>
140
+
141
+ <TYPESCRIPT_EXAMPLE>
142
+ // TypeScript interface contract
143
+ interface User {
144
+ id: number;
145
+ email: string;
146
+ username: string;
147
+ isActive: boolean;
148
+ role?: string;
149
+ }
150
+
151
+ interface AuthToken {
152
+ token: string;
153
+ expiresAt: Date;
154
+ userId: number;
155
+ }
156
+
157
+ // Save as: write_file("contracts/auth_types.ts", content)
158
+ </TYPESCRIPT_EXAMPLE>
159
+
160
+ <RUST_EXAMPLE>
161
+ // Rust struct contract
162
+ use serde::{Deserialize, Serialize};
163
+
164
+ #[derive(Debug, Serialize, Deserialize)]
165
+ pub struct User {
166
+ pub id: u64,
167
+ pub email: String,
168
+ pub username: String,
169
+ pub is_active: bool,
170
+ pub role: Option<String>,
171
+ }
172
+
173
+ // Save as: write_file("contracts/user_types.rs", content)
174
+ </RUST_EXAMPLE>
175
+
176
+ <OPENAPI_EXAMPLE>
177
+ {
178
+ "openapi": "3.0.0",
179
+ "info": {
180
+ "title": "User API",
181
+ "version": "1.0.0"
182
+ },
183
+ "paths": {
184
+ "/users": {
185
+ "get": {
186
+ "summary": "List users",
187
+ "responses": {
188
+ "200": {
189
+ "description": "Successful response",
190
+ "content": {
191
+ "application/json": {
192
+ "schema": {
193
+ "type": "array",
194
+ "items": { "$ref": "#/components/schemas/User" }
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ },
203
+ "components": {
204
+ "schemas": {
205
+ "User": {
206
+ "type": "object",
207
+ "properties": {
208
+ "id": { "type": "integer" },
209
+ "email": { "type": "string" },
210
+ "username": { "type": "string" }
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ // Save as: write_file("contracts/user_api.json", content)
218
+ </OPENAPI_EXAMPLE>
219
+
220
+ ## WHAT IS ALLOWED vs WHAT IS FORBIDDEN
221
+
222
+ **✅ ALLOWED - Type Definitions (Shape and Structure):**
223
+
224
+ ```python
225
+ # ✅ GOOD: Pydantic model (type definition)
226
+ from pydantic import BaseModel
227
+
228
+ class User(BaseModel):
229
+ id: int
230
+ email: str
231
+ username: str
232
+
233
+ # ✅ GOOD: Protocol class (interface definition)
234
+ from typing import Protocol
235
+
236
+ class Storage(Protocol):
237
+ def save(self, data: str) -> None: ...
238
+ def load(self) -> str: ...
239
+
240
+ # ✅ GOOD: Type aliases and enums
241
+ from typing import Literal
242
+ from enum import Enum
243
+
244
+ UserRole = Literal["admin", "user", "guest"]
245
+
246
+ class Status(Enum):
247
+ ACTIVE = "active"
248
+ INACTIVE = "inactive"
249
+ ```
250
+
251
+ **❌ FORBIDDEN - Executable Code (Behavior and Logic):**
252
+
253
+ ```python
254
+ # ❌ BAD: Function with pass (executable code)
255
+ def main() -> int:
256
+ pass
257
+
258
+ # ❌ BAD: Function with implementation
259
+ def validate_input(x: str) -> str:
260
+ return x.strip()
261
+
262
+ # ❌ BAD: Class with method implementations
263
+ class HistoryManager:
264
+ def __init__(self):
265
+ pass
266
+
267
+ def add_message(self, msg: str):
268
+ pass
269
+
270
+ # ❌ BAD: Data constants (runtime values)
271
+ SUPPORTED_PROVIDERS = [
272
+ {"name": "openai", "key": "OPENAI_API_KEY"}
273
+ ]
274
+
275
+ # ❌ BAD: Helper functions
276
+ def get_default_config() -> dict:
277
+ return {"model": "gpt-4"}
278
+
279
+ # ❌ BAD: Executable code blocks
280
+ if __name__ == "__main__":
281
+ main()
282
+ ```
283
+
284
+ **Remember**: Contracts define **SHAPES** (types, interfaces, schemas), NOT **BEHAVIOR** (functions, logic, implementations).
285
+
19
286
  ## AI AGENT PIPELINE AWARENESS
20
287
 
21
288
  **CRITICAL**: Your output will be consumed by AI coding agents (Claude Code, Cursor, Windsurf, etc.)
@@ -1,5 +1,7 @@
1
1
  ## System Status
2
2
 
3
+ Your training data may be old. The current date and time is: {{ current_datetime }} in {{ timezone_name }} (UTC{{ utc_offset }})
4
+
3
5
  {% include 'agents/state/codebase/codebase_graphs_available.j2' %}
4
6
 
5
7
  ## Available Files
@@ -20,6 +22,8 @@ No files currently exist in your allowed directories. You can create:
20
22
  - `exports/` folder - For exported documents
21
23
  {% endif %}
22
24
 
25
+ When updating a file try to add into the footer that this was created using Shotgun (https://shotgun.sh).
26
+
23
27
  {% if markdown_toc %}
24
28
  ## Document Table of Contents - READ THE ENTIRE FILE TO UNDERSTAND MORE
25
29
 
@@ -99,7 +99,7 @@ Then organize tasks into logical sections:
99
99
  USER INTERACTION - ASK CLARIFYING QUESTIONS:
100
100
 
101
101
  - ALWAYS ask clarifying questions when the request is vague or ambiguous
102
- - Use ask_user tool to gather specific details about:
102
+ - Use clarifying questions to gather specific details about:
103
103
  - Specific features or functionality to prioritize
104
104
  - Technical constraints or preferences
105
105
  - Timeline and resource constraints
shotgun/prompts/loader.py CHANGED
@@ -5,7 +5,7 @@ from datetime import datetime
5
5
  from pathlib import Path
6
6
  from typing import Any
7
7
 
8
- from jinja2 import Environment, FileSystemLoader, Template, select_autoescape
8
+ from jinja2 import Environment, FileSystemLoader, Template
9
9
 
10
10
  from shotgun.logging_config import setup_logger
11
11
 
@@ -32,7 +32,7 @@ class PromptLoader:
32
32
  self.templates_dir = templates_dir
33
33
  self.env = Environment(
34
34
  loader=FileSystemLoader(str(templates_dir)),
35
- autoescape=select_autoescape(["j2"]),
35
+ autoescape=False, # noqa: S701 - These are LLM prompts, not HTML
36
36
  trim_blocks=True,
37
37
  lstrip_blocks=True,
38
38
  )
@@ -0,0 +1,14 @@
1
+ Your training data may be old. The current date and time is: {{ current_datetime }} ({{ timezone_name }}, {{ utc_offset }})
2
+
3
+ Please provide current and accurate information about the following query:
4
+
5
+ Query: {{ query }}
6
+
7
+ Instructions:
8
+ - Provide comprehensive, factual information
9
+ - Include relevant details and context
10
+ - Focus on current and recent information
11
+ - Be specific and accurate in your response
12
+ - You can't ask the user for details, so assume the most relevant details for the query
13
+
14
+ ALWAYS PROVIDE THE SOURCES (urls) TO BACK UP THE INFORMATION YOU PROVIDE.
@@ -1,9 +1,8 @@
1
1
  """Sentry observability setup for Shotgun."""
2
2
 
3
- import os
4
-
5
3
  from shotgun import __version__
6
4
  from shotgun.logging_config import get_early_logger
5
+ from shotgun.settings import settings
7
6
 
8
7
  # Use early logger to prevent automatic StreamHandler creation
9
8
  logger = get_early_logger(__name__)
@@ -23,24 +22,14 @@ def setup_sentry_observability() -> bool:
23
22
  logger.debug("Sentry is already initialized, skipping")
24
23
  return True
25
24
 
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")
25
+ # Get DSN from settings (handles build constants + env vars automatically)
26
+ dsn = settings.telemetry.sentry_dsn
38
27
 
39
28
  if not dsn:
40
29
  logger.debug("No Sentry DSN configured, skipping Sentry initialization")
41
30
  return False
42
31
 
43
- logger.debug("Found DSN, proceeding with Sentry setup")
32
+ logger.debug("Using Sentry DSN from settings, proceeding with setup")
44
33
 
45
34
  # Determine environment based on version
46
35
  # Dev versions contain "dev", "rc", "alpha", or "beta"
shotgun/settings.py ADDED
@@ -0,0 +1,238 @@
1
+ """Centralized application settings using Pydantic Settings.
2
+
3
+ All environment variables use the SHOTGUN_ prefix to avoid conflicts with other tools.
4
+ Settings are loaded with the following priority:
5
+ 1. Environment variables (highest priority)
6
+ 2. Build constants (embedded at build time)
7
+ 3. Default values (lowest priority)
8
+
9
+ Example usage:
10
+ from shotgun.settings import settings
11
+
12
+ # Access telemetry settings
13
+ if settings.telemetry.sentry_dsn:
14
+ sentry_sdk.init(dsn=settings.telemetry.sentry_dsn)
15
+
16
+ # Access logging settings
17
+ logger.setLevel(settings.logging.log_level)
18
+
19
+ # Access API settings
20
+ response = httpx.get(settings.api.web_base_url)
21
+ """
22
+
23
+ from typing import Any
24
+
25
+ from pydantic import Field, field_validator
26
+ from pydantic_settings import BaseSettings, SettingsConfigDict
27
+
28
+
29
+ def _get_build_constant(name: str, default: Any = None) -> Any:
30
+ """Get a value from build_constants.py, falling back to default.
31
+
32
+ Args:
33
+ name: The constant name to retrieve (e.g., "SENTRY_DSN")
34
+ default: Default value if constant not found
35
+
36
+ Returns:
37
+ The constant value, or default if not found/import fails
38
+ """
39
+ try:
40
+ from shotgun import build_constants
41
+
42
+ return getattr(build_constants, name, default)
43
+ except ImportError:
44
+ return default
45
+
46
+
47
+ class TelemetrySettings(BaseSettings):
48
+ """Telemetry and observability settings.
49
+
50
+ These settings control error tracking (Sentry), analytics (PostHog),
51
+ and observability (Logfire) integrations.
52
+ """
53
+
54
+ sentry_dsn: str = Field(
55
+ default_factory=lambda: _get_build_constant("SENTRY_DSN", ""),
56
+ description="Sentry DSN for error tracking",
57
+ )
58
+ posthog_api_key: str = Field(
59
+ default_factory=lambda: _get_build_constant("POSTHOG_API_KEY", ""),
60
+ description="PostHog API key for analytics",
61
+ )
62
+ posthog_project_id: str = Field(
63
+ default_factory=lambda: _get_build_constant("POSTHOG_PROJECT_ID", ""),
64
+ description="PostHog project ID",
65
+ )
66
+ logfire_enabled: bool = Field(
67
+ default_factory=lambda: _get_build_constant("LOGFIRE_ENABLED", False),
68
+ description="Enable Logfire observability (dev builds only)",
69
+ )
70
+ logfire_token: str = Field(
71
+ default_factory=lambda: _get_build_constant("LOGFIRE_TOKEN", ""),
72
+ description="Logfire authentication token",
73
+ )
74
+
75
+ model_config = SettingsConfigDict(
76
+ env_prefix="SHOTGUN_",
77
+ env_file=".env",
78
+ env_file_encoding="utf-8",
79
+ extra="ignore",
80
+ )
81
+
82
+ @field_validator("logfire_enabled", mode="before")
83
+ @classmethod
84
+ def parse_bool(cls, v: Any) -> bool:
85
+ """Parse boolean values from strings (matches is_truthy behavior)."""
86
+ if isinstance(v, bool):
87
+ return v
88
+ if isinstance(v, str):
89
+ return v.lower() in ("true", "1", "yes")
90
+ return bool(v)
91
+
92
+
93
+ class LoggingSettings(BaseSettings):
94
+ """Logging configuration settings.
95
+
96
+ Controls log level, console output, and file logging behavior.
97
+ """
98
+
99
+ log_level: str = Field(
100
+ default="INFO",
101
+ description="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
102
+ )
103
+ logging_to_console: bool = Field(
104
+ default=False,
105
+ description="Enable console logging output",
106
+ )
107
+ logging_to_file: bool = Field(
108
+ default=True,
109
+ description="Enable file logging output",
110
+ )
111
+
112
+ model_config = SettingsConfigDict(
113
+ env_prefix="SHOTGUN_",
114
+ env_file=".env",
115
+ env_file_encoding="utf-8",
116
+ extra="ignore",
117
+ )
118
+
119
+ @field_validator("log_level")
120
+ @classmethod
121
+ def validate_log_level(cls, v: str) -> str:
122
+ """Validate log level is one of the allowed values."""
123
+ v = v.upper()
124
+ valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
125
+ if v not in valid_levels:
126
+ return "INFO" # Default to INFO if invalid
127
+ return v
128
+
129
+ @field_validator("logging_to_console", "logging_to_file", mode="before")
130
+ @classmethod
131
+ def parse_bool(cls, v: Any) -> bool:
132
+ """Parse boolean values from strings (matches is_truthy behavior)."""
133
+ if isinstance(v, bool):
134
+ return v
135
+ if isinstance(v, str):
136
+ return v.lower() in ("true", "1", "yes")
137
+ return bool(v)
138
+
139
+
140
+ class ApiSettings(BaseSettings):
141
+ """API endpoint settings.
142
+
143
+ Configuration for Shotgun backend services.
144
+ """
145
+
146
+ web_base_url: str = Field(
147
+ default="https://api-219702594231.us-east4.run.app",
148
+ description="Shotgun Web API base URL (authentication/subscription)",
149
+ )
150
+ account_llm_base_url: str = Field(
151
+ default="https://litellm-219702594231.us-east4.run.app",
152
+ description="Shotgun's LiteLLM proxy base URL (AI model requests)",
153
+ )
154
+
155
+ model_config = SettingsConfigDict(
156
+ env_prefix="SHOTGUN_",
157
+ env_file=".env",
158
+ env_file_encoding="utf-8",
159
+ extra="ignore",
160
+ )
161
+
162
+
163
+ class DevelopmentSettings(BaseSettings):
164
+ """Development and testing settings.
165
+
166
+ These settings are primarily used for testing and development purposes.
167
+ """
168
+
169
+ home: str | None = Field(
170
+ default=None,
171
+ description="Override Shotgun home directory (for testing)",
172
+ )
173
+ pipx_simulate: bool = Field(
174
+ default=False,
175
+ description="Simulate pipx installation (for testing)",
176
+ )
177
+
178
+ model_config = SettingsConfigDict(
179
+ env_prefix="SHOTGUN_",
180
+ env_file=".env",
181
+ env_file_encoding="utf-8",
182
+ extra="ignore",
183
+ )
184
+
185
+ @field_validator("pipx_simulate", mode="before")
186
+ @classmethod
187
+ def parse_bool(cls, v: Any) -> bool:
188
+ """Parse boolean values from strings (matches is_truthy behavior)."""
189
+ if isinstance(v, bool):
190
+ return v
191
+ if isinstance(v, str):
192
+ return v.lower() in ("true", "1", "yes")
193
+ return bool(v)
194
+
195
+
196
+ class Settings(BaseSettings):
197
+ """Main application settings with SHOTGUN_ prefix.
198
+
199
+ This is the main settings class that composes all other settings groups.
200
+ Access settings via the global `settings` singleton instance.
201
+
202
+ Example:
203
+ from shotgun.settings import settings
204
+
205
+ # Telemetry settings
206
+ settings.telemetry.sentry_dsn
207
+ settings.telemetry.posthog_api_key
208
+ settings.telemetry.logfire_enabled
209
+
210
+ # Logging settings
211
+ settings.logging.log_level
212
+ settings.logging.logging_to_console
213
+
214
+ # API settings
215
+ settings.api.web_base_url
216
+ settings.api.account_llm_base_url
217
+
218
+ # Development settings
219
+ settings.dev.home
220
+ settings.dev.pipx_simulate
221
+ """
222
+
223
+ telemetry: TelemetrySettings = Field(default_factory=TelemetrySettings)
224
+ logging: LoggingSettings = Field(default_factory=LoggingSettings)
225
+ api: ApiSettings = Field(default_factory=ApiSettings)
226
+ dev: DevelopmentSettings = Field(default_factory=DevelopmentSettings)
227
+
228
+ model_config = SettingsConfigDict(
229
+ env_prefix="SHOTGUN_",
230
+ env_file=".env",
231
+ env_file_encoding="utf-8",
232
+ extra="ignore",
233
+ )
234
+
235
+
236
+ # Global settings singleton
237
+ # Import this in your modules: from shotgun.settings import settings
238
+ settings = Settings()