shotgun-sh 0.2.17__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 +28 -14
- shotgun/agents/common.py +1 -1
- shotgun/agents/config/README.md +89 -0
- shotgun/agents/config/__init__.py +10 -1
- shotgun/agents/config/manager.py +323 -53
- shotgun/agents/config/models.py +85 -21
- shotgun/agents/config/provider.py +51 -13
- shotgun/agents/config/streaming_test.py +119 -0
- shotgun/agents/context_analyzer/analyzer.py +6 -2
- 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 +27 -1
- 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 +267 -3
- shotgun/agents/{history → conversation/history}/token_counting/anthropic.py +8 -0
- shotgun/agents/{conversation_manager.py → conversation/manager.py} +1 -1
- shotgun/agents/{conversation_history.py → conversation/models.py} +8 -94
- shotgun/agents/error/__init__.py +11 -0
- shotgun/agents/error/models.py +19 -0
- shotgun/agents/runner.py +230 -0
- shotgun/agents/tools/web_search/openai.py +1 -1
- shotgun/build_constants.py +2 -2
- shotgun/cli/clear.py +1 -1
- shotgun/cli/compact.py +5 -3
- shotgun/cli/context.py +44 -1
- shotgun/cli/error_handler.py +24 -0
- shotgun/cli/export.py +34 -34
- shotgun/cli/plan.py +34 -34
- shotgun/cli/research.py +17 -9
- 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/codebase/core/ingestor.py +153 -7
- shotgun/codebase/models.py +2 -0
- shotgun/exceptions.py +325 -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 +42 -0
- shotgun/main.py +4 -0
- shotgun/posthog_telemetry.py +1 -1
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +28 -3
- 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/settings.py +5 -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/tui/app.py +73 -9
- shotgun/tui/containers.py +1 -1
- shotgun/tui/layout.py +5 -0
- shotgun/tui/screens/chat/chat_screen.py +372 -95
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +196 -17
- shotgun/tui/screens/chat_screen/command_providers.py +13 -2
- shotgun/tui/screens/chat_screen/hint_message.py +76 -1
- shotgun/tui/screens/confirmation_dialog.py +40 -0
- shotgun/tui/screens/directory_setup.py +45 -41
- shotgun/tui/screens/feedback.py +10 -3
- shotgun/tui/screens/github_issue.py +11 -2
- shotgun/tui/screens/model_picker.py +28 -8
- shotgun/tui/screens/onboarding.py +149 -0
- shotgun/tui/screens/pipx_migration.py +58 -6
- shotgun/tui/screens/provider_config.py +66 -8
- 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 +110 -16
- shotgun/tui/screens/spec_pull.py +288 -0
- shotgun/tui/screens/welcome.py +123 -0
- shotgun/tui/services/conversation_service.py +5 -2
- shotgun/tui/widgets/widget_coordinator.py +1 -1
- {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/METADATA +9 -2
- {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/RECORD +112 -77
- {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/WHEEL +1 -1
- /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_counting/base.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/openai.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/sentencepiece_counter.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/tokenizer_cache.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/utils.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_estimation.py +0 -0
- {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""HTTP client for LiteLLM Proxy API."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
from tenacity import (
|
|
8
|
+
before_sleep_log,
|
|
9
|
+
retry,
|
|
10
|
+
retry_if_exception,
|
|
11
|
+
stop_after_attempt,
|
|
12
|
+
wait_exponential_jitter,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from shotgun.api_endpoints import LITELLM_PROXY_BASE_URL
|
|
16
|
+
from shotgun.logging_config import get_logger
|
|
17
|
+
|
|
18
|
+
from .models import BudgetInfo, KeyInfoResponse, TeamInfoResponse
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _is_retryable_http_error(exception: BaseException) -> bool:
|
|
24
|
+
"""Check if HTTP exception should trigger a retry.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
exception: The exception to check
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
True if the exception is a transient error that should be retried
|
|
31
|
+
"""
|
|
32
|
+
# Retry on network errors and timeouts
|
|
33
|
+
if isinstance(exception, (httpx.RequestError, httpx.TimeoutException)):
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
# Retry on server errors (5xx) and rate limits (429)
|
|
37
|
+
if isinstance(exception, httpx.HTTPStatusError):
|
|
38
|
+
status_code = exception.response.status_code
|
|
39
|
+
return status_code >= 500 or status_code == 429
|
|
40
|
+
|
|
41
|
+
# Don't retry on other errors (e.g., 4xx client errors)
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class LiteLLMProxyClient:
|
|
46
|
+
"""HTTP client for LiteLLM Proxy API.
|
|
47
|
+
|
|
48
|
+
Provides methods to query budget information and key/team metadata
|
|
49
|
+
from a LiteLLM proxy server.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
api_key: str,
|
|
55
|
+
base_url: str | None = None,
|
|
56
|
+
timeout: float = 10.0,
|
|
57
|
+
):
|
|
58
|
+
"""Initialize LiteLLM Proxy client.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
api_key: LiteLLM API key for authentication
|
|
62
|
+
base_url: Base URL for LiteLLM proxy. If None, uses LITELLM_PROXY_BASE_URL
|
|
63
|
+
timeout: Request timeout in seconds
|
|
64
|
+
"""
|
|
65
|
+
self.api_key = api_key
|
|
66
|
+
self.base_url = base_url or LITELLM_PROXY_BASE_URL
|
|
67
|
+
self.timeout = timeout
|
|
68
|
+
|
|
69
|
+
@retry(
|
|
70
|
+
stop=stop_after_attempt(3),
|
|
71
|
+
wait=wait_exponential_jitter(initial=1, max=8),
|
|
72
|
+
retry=retry_if_exception(_is_retryable_http_error),
|
|
73
|
+
before_sleep=before_sleep_log(logger, logging.WARNING),
|
|
74
|
+
reraise=True,
|
|
75
|
+
)
|
|
76
|
+
async def _request_with_retry(
|
|
77
|
+
self,
|
|
78
|
+
method: str,
|
|
79
|
+
url: str,
|
|
80
|
+
**kwargs: Any,
|
|
81
|
+
) -> httpx.Response:
|
|
82
|
+
"""Make async HTTP request with exponential backoff retry and jitter.
|
|
83
|
+
|
|
84
|
+
Uses tenacity to retry on transient errors (5xx, 429, network errors)
|
|
85
|
+
with exponential backoff and jitter. Client errors (4xx except 429)
|
|
86
|
+
are not retried.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
method: HTTP method (GET, POST, etc.)
|
|
90
|
+
url: Request URL
|
|
91
|
+
**kwargs: Additional arguments to pass to httpx request
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
HTTP response
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
httpx.HTTPError: If request fails after all retries
|
|
98
|
+
"""
|
|
99
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
100
|
+
response = await client.request(method, url, **kwargs)
|
|
101
|
+
response.raise_for_status()
|
|
102
|
+
return response
|
|
103
|
+
|
|
104
|
+
async def get_key_info(self) -> KeyInfoResponse:
|
|
105
|
+
"""Get key information from LiteLLM proxy.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Key information including spend, budget, and team_id
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
httpx.HTTPError: If request fails
|
|
112
|
+
"""
|
|
113
|
+
url = f"{self.base_url}/key/info"
|
|
114
|
+
params = {"key": self.api_key}
|
|
115
|
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
116
|
+
|
|
117
|
+
logger.debug("Fetching key info from %s", url)
|
|
118
|
+
|
|
119
|
+
response = await self._request_with_retry(
|
|
120
|
+
"GET", url, params=params, headers=headers
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
data = response.json()
|
|
124
|
+
result = KeyInfoResponse.model_validate(data)
|
|
125
|
+
|
|
126
|
+
logger.info(
|
|
127
|
+
"Successfully fetched key info: key_alias=%s, team_id=%s",
|
|
128
|
+
result.info.key_alias,
|
|
129
|
+
result.info.team_id,
|
|
130
|
+
)
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
async def get_team_info(self, team_id: str) -> TeamInfoResponse:
|
|
134
|
+
"""Get team information from LiteLLM proxy.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
team_id: Team identifier
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Team information including spend and budget
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
httpx.HTTPError: If request fails
|
|
144
|
+
"""
|
|
145
|
+
url = f"{self.base_url}/team/info"
|
|
146
|
+
params = {"team_id": team_id}
|
|
147
|
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
148
|
+
|
|
149
|
+
logger.debug("Fetching team info from %s for team_id=%s", url, team_id)
|
|
150
|
+
|
|
151
|
+
response = await self._request_with_retry(
|
|
152
|
+
"GET", url, params=params, headers=headers
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
data = response.json()
|
|
156
|
+
result = TeamInfoResponse.model_validate(data)
|
|
157
|
+
|
|
158
|
+
logger.info(
|
|
159
|
+
"Successfully fetched team info: team_alias=%s",
|
|
160
|
+
result.team_info.team_alias,
|
|
161
|
+
)
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
async def get_budget_info(self) -> BudgetInfo:
|
|
165
|
+
"""Get team-level budget information for this key.
|
|
166
|
+
|
|
167
|
+
Budget is always configured at the team level, never at the key level.
|
|
168
|
+
This method fetches the team_id from the key info, then retrieves
|
|
169
|
+
the team's budget information.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Team-level budget information
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
httpx.HTTPError: If request fails
|
|
176
|
+
ValueError: If team has no budget configured
|
|
177
|
+
"""
|
|
178
|
+
logger.debug("Fetching budget info")
|
|
179
|
+
|
|
180
|
+
# Get key info to retrieve team_id
|
|
181
|
+
key_response = await self.get_key_info()
|
|
182
|
+
key_info = key_response.info
|
|
183
|
+
|
|
184
|
+
# Fetch team budget (budget is always at team level)
|
|
185
|
+
logger.debug(
|
|
186
|
+
"Fetching team budget for team_id=%s",
|
|
187
|
+
key_info.team_id,
|
|
188
|
+
)
|
|
189
|
+
team_response = await self.get_team_info(key_info.team_id)
|
|
190
|
+
team_info = team_response.team_info
|
|
191
|
+
|
|
192
|
+
if team_info.max_budget is None:
|
|
193
|
+
raise ValueError(
|
|
194
|
+
f"Team (team_id={key_info.team_id}) has no max_budget configured"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
logger.debug("Using team-level budget: $%.6f", team_info.max_budget)
|
|
198
|
+
return BudgetInfo.from_team_info(team_info)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# Convenience function for standalone use
|
|
202
|
+
async def get_budget_info(api_key: str, base_url: str | None = None) -> BudgetInfo:
|
|
203
|
+
"""Get budget information for an API key.
|
|
204
|
+
|
|
205
|
+
Convenience function that creates a client and calls get_budget_info.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
api_key: LiteLLM API key
|
|
209
|
+
base_url: Optional base URL for LiteLLM proxy
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Budget information
|
|
213
|
+
"""
|
|
214
|
+
client = LiteLLMProxyClient(api_key, base_url=base_url)
|
|
215
|
+
return await client.get_budget_info()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Pydantic models for LiteLLM Proxy API."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BudgetSource(StrEnum):
|
|
9
|
+
"""Source of budget information."""
|
|
10
|
+
|
|
11
|
+
KEY = "key"
|
|
12
|
+
TEAM = "team"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class KeyInfoData(BaseModel):
|
|
16
|
+
"""Key information data from /key/info endpoint."""
|
|
17
|
+
|
|
18
|
+
key_name: str = Field(description="Key name/identifier")
|
|
19
|
+
key_alias: str | None = Field(default=None, description="Human-readable key alias")
|
|
20
|
+
spend: float = Field(description="Current spend for this key in USD")
|
|
21
|
+
max_budget: float | None = Field(
|
|
22
|
+
default=None, description="Maximum budget for this key in USD"
|
|
23
|
+
)
|
|
24
|
+
team_id: str = Field(description="Team ID associated with this key")
|
|
25
|
+
user_id: str = Field(description="User ID associated with this key")
|
|
26
|
+
models: list[str] = Field(
|
|
27
|
+
default_factory=list, description="List of models available to this key"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class KeyInfoResponse(BaseModel):
|
|
32
|
+
"""Response from /key/info endpoint."""
|
|
33
|
+
|
|
34
|
+
key: str = Field(description="The API key")
|
|
35
|
+
info: KeyInfoData = Field(description="Key information data")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TeamInfoData(BaseModel):
|
|
39
|
+
"""Team information data from /team/info endpoint."""
|
|
40
|
+
|
|
41
|
+
team_id: str = Field(description="Team identifier")
|
|
42
|
+
team_alias: str | None = Field(
|
|
43
|
+
default=None, description="Human-readable team alias"
|
|
44
|
+
)
|
|
45
|
+
max_budget: float | None = Field(
|
|
46
|
+
default=None, description="Maximum budget for this team in USD"
|
|
47
|
+
)
|
|
48
|
+
spend: float = Field(description="Current spend for this team in USD")
|
|
49
|
+
models: list[str] = Field(
|
|
50
|
+
default_factory=list, description="List of models available to this team"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TeamInfoResponse(BaseModel):
|
|
55
|
+
"""Response from /team/info endpoint."""
|
|
56
|
+
|
|
57
|
+
team_id: str = Field(description="Team identifier")
|
|
58
|
+
team_info: TeamInfoData = Field(description="Team information data")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BudgetInfo(BaseModel):
|
|
62
|
+
"""Unified budget information.
|
|
63
|
+
|
|
64
|
+
Combines key and team budget information to provide a single view
|
|
65
|
+
of budget status. Budget can come from either key-level or team-level,
|
|
66
|
+
with key-level taking priority if set.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
max_budget: float = Field(description="Maximum budget in USD")
|
|
70
|
+
spend: float = Field(description="Current spend in USD")
|
|
71
|
+
remaining: float = Field(description="Remaining budget in USD")
|
|
72
|
+
source: BudgetSource = Field(
|
|
73
|
+
description="Source of budget information (key or team)"
|
|
74
|
+
)
|
|
75
|
+
percentage_used: float = Field(description="Percentage of budget used (0-100)")
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_key_info(cls, key_info: KeyInfoData) -> "BudgetInfo":
|
|
79
|
+
"""Create BudgetInfo from key-level budget.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
key_info: Key information containing budget data
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
BudgetInfo instance with key-level budget
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: If key does not have max_budget set
|
|
89
|
+
"""
|
|
90
|
+
if key_info.max_budget is None:
|
|
91
|
+
raise ValueError("Key does not have max_budget set")
|
|
92
|
+
|
|
93
|
+
remaining = key_info.max_budget - key_info.spend
|
|
94
|
+
percentage_used = (
|
|
95
|
+
(key_info.spend / key_info.max_budget * 100)
|
|
96
|
+
if key_info.max_budget > 0
|
|
97
|
+
else 0.0
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return cls(
|
|
101
|
+
max_budget=key_info.max_budget,
|
|
102
|
+
spend=key_info.spend,
|
|
103
|
+
remaining=remaining,
|
|
104
|
+
source=BudgetSource.KEY,
|
|
105
|
+
percentage_used=percentage_used,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def from_team_info(cls, team_info: TeamInfoData) -> "BudgetInfo":
|
|
110
|
+
"""Create BudgetInfo from team-level budget.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
team_info: Team information containing budget data
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
BudgetInfo instance with team-level budget
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ValueError: If team does not have max_budget set
|
|
120
|
+
"""
|
|
121
|
+
if team_info.max_budget is None:
|
|
122
|
+
raise ValueError("Team does not have max_budget set")
|
|
123
|
+
|
|
124
|
+
remaining = team_info.max_budget - team_info.spend
|
|
125
|
+
percentage_used = (
|
|
126
|
+
(team_info.spend / team_info.max_budget * 100)
|
|
127
|
+
if team_info.max_budget > 0
|
|
128
|
+
else 0.0
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return cls(
|
|
132
|
+
max_budget=team_info.max_budget,
|
|
133
|
+
spend=team_info.spend,
|
|
134
|
+
remaining=remaining,
|
|
135
|
+
source=BudgetSource.TEAM,
|
|
136
|
+
percentage_used=percentage_used,
|
|
137
|
+
)
|
shotgun/logging_config.py
CHANGED
|
@@ -27,6 +27,44 @@ def get_log_directory() -> Path:
|
|
|
27
27
|
return log_dir
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def cleanup_old_log_files(log_dir: Path, max_files: int) -> None:
|
|
31
|
+
"""Remove old log files, keeping only the most recent ones.
|
|
32
|
+
|
|
33
|
+
Also removes the legacy shotgun.log file if it exists.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
log_dir: Directory containing log files
|
|
37
|
+
max_files: Maximum number of log files to keep
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
# Remove legacy non-timestamped log file if it exists
|
|
41
|
+
legacy_log = log_dir / "shotgun.log"
|
|
42
|
+
if legacy_log.exists():
|
|
43
|
+
try:
|
|
44
|
+
legacy_log.unlink()
|
|
45
|
+
except OSError:
|
|
46
|
+
pass # noqa: S110
|
|
47
|
+
|
|
48
|
+
# Find all shotgun log files
|
|
49
|
+
log_files = sorted(
|
|
50
|
+
log_dir.glob("shotgun-*.log"),
|
|
51
|
+
key=lambda p: p.stat().st_mtime,
|
|
52
|
+
reverse=True, # Newest first
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Remove files beyond the limit
|
|
56
|
+
files_to_delete = log_files[max_files:]
|
|
57
|
+
for log_file in files_to_delete:
|
|
58
|
+
try:
|
|
59
|
+
log_file.unlink()
|
|
60
|
+
except OSError:
|
|
61
|
+
# Ignore errors when deleting individual files
|
|
62
|
+
pass # noqa: S110
|
|
63
|
+
except Exception: # noqa: S110
|
|
64
|
+
# Silently fail - log cleanup shouldn't break the application
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
30
68
|
class ColoredFormatter(logging.Formatter):
|
|
31
69
|
"""Custom formatter with colors for different log levels."""
|
|
32
70
|
|
|
@@ -123,6 +161,10 @@ def setup_logger(
|
|
|
123
161
|
try:
|
|
124
162
|
# Create file handler with ISO8601 timestamp for each run
|
|
125
163
|
log_dir = get_log_directory()
|
|
164
|
+
|
|
165
|
+
# Clean up old log files before creating a new one
|
|
166
|
+
cleanup_old_log_files(log_dir, settings.logging.max_log_files)
|
|
167
|
+
|
|
126
168
|
log_file = log_dir / f"shotgun-{_RUN_TIMESTAMP}.log"
|
|
127
169
|
|
|
128
170
|
# Use regular FileHandler - each run gets its own isolated log file
|
shotgun/main.py
CHANGED
|
@@ -32,6 +32,7 @@ from shotgun.cli import (
|
|
|
32
32
|
feedback,
|
|
33
33
|
plan,
|
|
34
34
|
research,
|
|
35
|
+
spec,
|
|
35
36
|
specify,
|
|
36
37
|
tasks,
|
|
37
38
|
update,
|
|
@@ -55,6 +56,8 @@ logger = get_logger(__name__)
|
|
|
55
56
|
logger.debug("Logfire observability enabled: %s", _logfire_enabled)
|
|
56
57
|
|
|
57
58
|
# Initialize configuration
|
|
59
|
+
# Note: If config migration fails, ConfigManager will auto-create fresh config
|
|
60
|
+
# and set migration_failed flag for user notification
|
|
58
61
|
try:
|
|
59
62
|
import asyncio
|
|
60
63
|
|
|
@@ -93,6 +96,7 @@ app.add_typer(tasks.app, name="tasks", help="Generate task lists with agentic ap
|
|
|
93
96
|
app.add_typer(export.app, name="export", help="Export artifacts to various formats")
|
|
94
97
|
app.add_typer(update.app, name="update", help="Check for and install updates")
|
|
95
98
|
app.add_typer(feedback.app, name="feedback", help="Send us feedback")
|
|
99
|
+
app.add_typer(spec.app, name="spec", help="Manage shared specifications")
|
|
96
100
|
|
|
97
101
|
|
|
98
102
|
def version_callback(value: bool) -> None:
|
shotgun/posthog_telemetry.py
CHANGED
|
@@ -8,7 +8,7 @@ from pydantic import BaseModel
|
|
|
8
8
|
|
|
9
9
|
from shotgun import __version__
|
|
10
10
|
from shotgun.agents.config import get_config_manager
|
|
11
|
-
from shotgun.agents.
|
|
11
|
+
from shotgun.agents.conversation import ConversationManager
|
|
12
12
|
from shotgun.logging_config import get_early_logger
|
|
13
13
|
from shotgun.settings import settings
|
|
14
14
|
|
|
@@ -4,14 +4,39 @@ Your extensive expertise spans, among other things:
|
|
|
4
4
|
* Software Architecture
|
|
5
5
|
* Software Development
|
|
6
6
|
|
|
7
|
+
## YOUR ROLE IN THE PIPELINE
|
|
8
|
+
|
|
9
|
+
**CRITICAL**: You are a DOCUMENTATION and PLANNING agent, NOT a coding/implementation agent.
|
|
10
|
+
|
|
11
|
+
- You produce DOCUMENTS (research, specifications, plans, tasks) that AI coding agents will consume
|
|
12
|
+
- You do NOT write production code, implement features, or make code changes
|
|
13
|
+
- NEVER offer to "move forward with implementation" or "start coding" - that's not your job
|
|
14
|
+
- NEVER ask "would you like me to implement this?" - implementation is done by separate AI coding tools
|
|
15
|
+
- Your deliverable is always a document file (.md), not code execution
|
|
16
|
+
- When your work is complete, the user will take your documents to a coding agent (Claude Code, Cursor, etc.)
|
|
17
|
+
|
|
18
|
+
## AGENT FILE PERMISSIONS
|
|
19
|
+
|
|
20
|
+
There are four agents in the pipeline, and each agent can ONLY write to specific files. The user can switch between agents using **Shift+Tab**.
|
|
21
|
+
|
|
22
|
+
The **Research agent** can only write to `research.md`. If the user asks about specifications, plans, or tasks, tell them: "Use **Shift+Tab** to switch to the [appropriate] agent which can edit that file for you."
|
|
23
|
+
|
|
24
|
+
The **Specification agent** can only write to `specification.md` and files inside the `.shotgun/contracts/` directory. If the user asks about research, plans, or tasks, tell them which agent handles that file.
|
|
25
|
+
|
|
26
|
+
The **Plan agent** can only write to `plan.md`. If the user asks about research, specifications, or tasks, tell them which agent handles that file.
|
|
27
|
+
|
|
28
|
+
The **Tasks agent** can only write to `tasks.md`. If the user asks about research, specifications, or plans, tell them which agent handles that file.
|
|
29
|
+
|
|
30
|
+
When a user asks you to edit a file you cannot write to, you MUST tell them which agent can help and how to switch: "I can't edit [filename] - that's handled by the [agent name] agent. Use **Shift+Tab** to switch to that agent and it can edit that file for you."
|
|
31
|
+
|
|
7
32
|
## KEY RULES
|
|
8
33
|
|
|
9
34
|
{% if interactive_mode %}
|
|
10
|
-
0.
|
|
35
|
+
0. Ask CLARIFYING QUESTIONS using structured output for complex or multi-step tasks when the request lacks sufficient detail.
|
|
11
36
|
- Return your response with the clarifying_questions field populated
|
|
12
|
-
-
|
|
37
|
+
- For simple, straightforward requests, make reasonable assumptions and proceed.
|
|
38
|
+
- Only ask the most critical questions to avoid overwhelming the user.
|
|
13
39
|
- Questions should be clear, specific, and answerable
|
|
14
|
-
- Do not ask too many questions that might overwhelm the user; prioritize the most important ones.
|
|
15
40
|
{% endif %}
|
|
16
41
|
1. Above all, prefer using tools to do the work and NEVER respond with text.
|
|
17
42
|
2. IMPORTANT: Always ask for review and go ahead to move forward after using write_file().
|
|
@@ -19,10 +19,10 @@ You must return responses using this structured format:
|
|
|
19
19
|
|
|
20
20
|
## When to Use Clarifying Questions
|
|
21
21
|
|
|
22
|
-
- BEFORE GETTING TO WORK:
|
|
22
|
+
- BEFORE GETTING TO WORK: For complex or multi-step tasks where the request is ambiguous or lacks sufficient detail, use clarifying_questions to ask what they want
|
|
23
23
|
- DURING WORK: After using write_file(), you can suggest that the user review it and ask any clarifying questions with clarifying_questions
|
|
24
|
-
-
|
|
25
|
-
-
|
|
24
|
+
- For simple, straightforward requests, make reasonable assumptions and proceed
|
|
25
|
+
- Only ask critical questions that significantly impact the outcome
|
|
26
26
|
|
|
27
27
|
## Important Notes
|
|
28
28
|
|
shotgun/prompts/agents/plan.j2
CHANGED
|
@@ -6,6 +6,22 @@ Your job is to help create comprehensive, actionable plans for software projects
|
|
|
6
6
|
|
|
7
7
|
{% include 'agents/partials/common_agent_system_prompt.j2' %}
|
|
8
8
|
|
|
9
|
+
## YOUR SCOPE AND HANDOFFS
|
|
10
|
+
|
|
11
|
+
You are the **Plan agent**. Your file is `plan.md` - this is the ONLY file you can write to.
|
|
12
|
+
|
|
13
|
+
When your plan is complete, suggest the next step:
|
|
14
|
+
"I've completed the plan. Use **Shift+Tab** to switch to the tasks agent to break this plan into actionable tasks."
|
|
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 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."
|
|
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, specifications, 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: `plan.md`
|
|
@@ -4,6 +4,22 @@ Your job is to help the user research various subjects related to their software
|
|
|
4
4
|
|
|
5
5
|
{% include 'agents/partials/common_agent_system_prompt.j2' %}
|
|
6
6
|
|
|
7
|
+
## YOUR SCOPE AND HANDOFFS
|
|
8
|
+
|
|
9
|
+
You are the **Research agent**. Your file is `research.md` - this is the ONLY file you can write to.
|
|
10
|
+
|
|
11
|
+
When your research is complete, suggest the next step:
|
|
12
|
+
"I've completed the research and updated research.md. Use **Shift+Tab** to switch to the specification agent to create the specification based on this research."
|
|
13
|
+
|
|
14
|
+
If the user asks you to edit other files, redirect them helpfully:
|
|
15
|
+
- 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."
|
|
16
|
+
- 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."
|
|
17
|
+
- 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."
|
|
18
|
+
|
|
19
|
+
NEVER offer to do work outside your scope:
|
|
20
|
+
- Don't offer to write specifications, plans, or tasks - 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: `research.md`
|
|
@@ -38,9 +54,6 @@ For research tasks:
|
|
|
38
54
|
|
|
39
55
|
## RESEARCH PRINCIPLES
|
|
40
56
|
|
|
41
|
-
{% if interactive_mode -%}
|
|
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
|
-
{% endif -%}
|
|
44
57
|
- Build upon existing research rather than starting from scratch
|
|
45
58
|
- Focus on practical, actionable information over theoretical concepts
|
|
46
59
|
- Include specific examples, tools, and implementation details
|
|
@@ -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`
|