shotgun-sh 0.1.0__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.
- shotgun/__init__.py +5 -0
- shotgun/agents/__init__.py +1 -0
- shotgun/agents/agent_manager.py +651 -0
- shotgun/agents/common.py +549 -0
- shotgun/agents/config/__init__.py +13 -0
- shotgun/agents/config/constants.py +17 -0
- shotgun/agents/config/manager.py +294 -0
- shotgun/agents/config/models.py +185 -0
- shotgun/agents/config/provider.py +206 -0
- shotgun/agents/conversation_history.py +106 -0
- shotgun/agents/conversation_manager.py +105 -0
- shotgun/agents/export.py +96 -0
- shotgun/agents/history/__init__.py +5 -0
- shotgun/agents/history/compaction.py +85 -0
- shotgun/agents/history/constants.py +19 -0
- shotgun/agents/history/context_extraction.py +108 -0
- shotgun/agents/history/history_building.py +104 -0
- shotgun/agents/history/history_processors.py +426 -0
- shotgun/agents/history/message_utils.py +84 -0
- shotgun/agents/history/token_counting.py +429 -0
- shotgun/agents/history/token_estimation.py +138 -0
- shotgun/agents/messages.py +35 -0
- shotgun/agents/models.py +275 -0
- shotgun/agents/plan.py +98 -0
- shotgun/agents/research.py +108 -0
- shotgun/agents/specify.py +98 -0
- shotgun/agents/tasks.py +96 -0
- shotgun/agents/tools/__init__.py +34 -0
- shotgun/agents/tools/codebase/__init__.py +28 -0
- shotgun/agents/tools/codebase/codebase_shell.py +256 -0
- shotgun/agents/tools/codebase/directory_lister.py +141 -0
- shotgun/agents/tools/codebase/file_read.py +144 -0
- shotgun/agents/tools/codebase/models.py +252 -0
- shotgun/agents/tools/codebase/query_graph.py +67 -0
- shotgun/agents/tools/codebase/retrieve_code.py +81 -0
- shotgun/agents/tools/file_management.py +218 -0
- shotgun/agents/tools/user_interaction.py +37 -0
- shotgun/agents/tools/web_search/__init__.py +60 -0
- shotgun/agents/tools/web_search/anthropic.py +144 -0
- shotgun/agents/tools/web_search/gemini.py +85 -0
- shotgun/agents/tools/web_search/openai.py +98 -0
- shotgun/agents/tools/web_search/utils.py +20 -0
- shotgun/build_constants.py +20 -0
- shotgun/cli/__init__.py +1 -0
- shotgun/cli/codebase/__init__.py +5 -0
- shotgun/cli/codebase/commands.py +202 -0
- shotgun/cli/codebase/models.py +21 -0
- shotgun/cli/config.py +275 -0
- shotgun/cli/export.py +81 -0
- shotgun/cli/models.py +10 -0
- shotgun/cli/plan.py +73 -0
- shotgun/cli/research.py +85 -0
- shotgun/cli/specify.py +69 -0
- shotgun/cli/tasks.py +78 -0
- shotgun/cli/update.py +152 -0
- shotgun/cli/utils.py +25 -0
- shotgun/codebase/__init__.py +12 -0
- shotgun/codebase/core/__init__.py +46 -0
- shotgun/codebase/core/change_detector.py +358 -0
- shotgun/codebase/core/code_retrieval.py +243 -0
- shotgun/codebase/core/ingestor.py +1497 -0
- shotgun/codebase/core/language_config.py +297 -0
- shotgun/codebase/core/manager.py +1662 -0
- shotgun/codebase/core/nl_query.py +331 -0
- shotgun/codebase/core/parser_loader.py +128 -0
- shotgun/codebase/models.py +111 -0
- shotgun/codebase/service.py +206 -0
- shotgun/logging_config.py +227 -0
- shotgun/main.py +167 -0
- shotgun/posthog_telemetry.py +158 -0
- shotgun/prompts/__init__.py +5 -0
- shotgun/prompts/agents/__init__.py +1 -0
- shotgun/prompts/agents/export.j2 +350 -0
- shotgun/prompts/agents/partials/codebase_understanding.j2 +87 -0
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +37 -0
- shotgun/prompts/agents/partials/content_formatting.j2 +65 -0
- shotgun/prompts/agents/partials/interactive_mode.j2 +26 -0
- shotgun/prompts/agents/plan.j2 +144 -0
- shotgun/prompts/agents/research.j2 +69 -0
- shotgun/prompts/agents/specify.j2 +51 -0
- shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +19 -0
- shotgun/prompts/agents/state/system_state.j2 +31 -0
- shotgun/prompts/agents/tasks.j2 +143 -0
- shotgun/prompts/codebase/__init__.py +1 -0
- shotgun/prompts/codebase/cypher_query_patterns.j2 +223 -0
- shotgun/prompts/codebase/cypher_system.j2 +28 -0
- shotgun/prompts/codebase/enhanced_query_context.j2 +10 -0
- shotgun/prompts/codebase/partials/cypher_rules.j2 +24 -0
- shotgun/prompts/codebase/partials/graph_schema.j2 +30 -0
- shotgun/prompts/codebase/partials/temporal_context.j2 +21 -0
- shotgun/prompts/history/__init__.py +1 -0
- shotgun/prompts/history/incremental_summarization.j2 +53 -0
- shotgun/prompts/history/summarization.j2 +46 -0
- shotgun/prompts/loader.py +140 -0
- shotgun/py.typed +0 -0
- shotgun/sdk/__init__.py +13 -0
- shotgun/sdk/codebase.py +219 -0
- shotgun/sdk/exceptions.py +17 -0
- shotgun/sdk/models.py +189 -0
- shotgun/sdk/services.py +23 -0
- shotgun/sentry_telemetry.py +87 -0
- shotgun/telemetry.py +93 -0
- shotgun/tui/__init__.py +0 -0
- shotgun/tui/app.py +116 -0
- shotgun/tui/commands/__init__.py +76 -0
- shotgun/tui/components/prompt_input.py +69 -0
- shotgun/tui/components/spinner.py +86 -0
- shotgun/tui/components/splash.py +25 -0
- shotgun/tui/components/vertical_tail.py +13 -0
- shotgun/tui/screens/chat.py +782 -0
- shotgun/tui/screens/chat.tcss +43 -0
- shotgun/tui/screens/chat_screen/__init__.py +0 -0
- shotgun/tui/screens/chat_screen/command_providers.py +219 -0
- shotgun/tui/screens/chat_screen/hint_message.py +40 -0
- shotgun/tui/screens/chat_screen/history.py +221 -0
- shotgun/tui/screens/directory_setup.py +113 -0
- shotgun/tui/screens/provider_config.py +221 -0
- shotgun/tui/screens/splash.py +31 -0
- shotgun/tui/styles.tcss +10 -0
- shotgun/tui/utils/__init__.py +5 -0
- shotgun/tui/utils/mode_progress.py +257 -0
- shotgun/utils/__init__.py +5 -0
- shotgun/utils/env_utils.py +35 -0
- shotgun/utils/file_system_utils.py +36 -0
- shotgun/utils/update_checker.py +375 -0
- shotgun_sh-0.1.0.dist-info/METADATA +466 -0
- shotgun_sh-0.1.0.dist-info/RECORD +130 -0
- shotgun_sh-0.1.0.dist-info/WHEEL +4 -0
- shotgun_sh-0.1.0.dist-info/entry_points.txt +2 -0
- shotgun_sh-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Code retrieval functionality for extracting source code from the knowledge graph."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from shotgun.logging_config import get_logger
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from shotgun.codebase.core.manager import CodebaseGraphManager
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CodeSnippet(BaseModel):
|
|
17
|
+
"""Model for code snippet retrieval results."""
|
|
18
|
+
|
|
19
|
+
qualified_name: str
|
|
20
|
+
source_code: str
|
|
21
|
+
file_path: str
|
|
22
|
+
line_start: int
|
|
23
|
+
line_end: int
|
|
24
|
+
found: bool = True
|
|
25
|
+
error_message: str | None = None
|
|
26
|
+
docstring: str | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def retrieve_code_by_qualified_name(
|
|
30
|
+
manager: "CodebaseGraphManager", graph_id: str, qualified_name: str
|
|
31
|
+
) -> CodeSnippet:
|
|
32
|
+
"""Retrieve code snippet by qualified name.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
manager: CodebaseGraphManager instance
|
|
36
|
+
graph_id: Graph ID to query
|
|
37
|
+
qualified_name: Fully qualified name of the entity
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
CodeSnippet with the retrieved code
|
|
41
|
+
"""
|
|
42
|
+
logger.info(f"Retrieving code for: {qualified_name}")
|
|
43
|
+
|
|
44
|
+
# Query to find the entity and its module
|
|
45
|
+
# The relationships in Kuzu are:
|
|
46
|
+
# - Module-[:DEFINES]->Class
|
|
47
|
+
# - Module-[:DEFINES]->Function
|
|
48
|
+
# - Class-[:DEFINES_METHOD]->Method
|
|
49
|
+
# For methods, we need to traverse back to the module
|
|
50
|
+
query = """
|
|
51
|
+
MATCH (n)
|
|
52
|
+
WHERE n.qualified_name = $qualified_name
|
|
53
|
+
OPTIONAL MATCH (m:Module)-[:DEFINES]->(n)
|
|
54
|
+
OPTIONAL MATCH (c:Class)-[:DEFINES_METHOD]->(n), (m2:Module)-[:DEFINES]->(c)
|
|
55
|
+
RETURN
|
|
56
|
+
n.name AS name,
|
|
57
|
+
n.line_start AS start_line,
|
|
58
|
+
n.line_end AS end_line,
|
|
59
|
+
COALESCE(m.path, m2.path) AS path,
|
|
60
|
+
COALESCE(n.docstring, NULL) AS docstring
|
|
61
|
+
LIMIT 1
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# Execute the query
|
|
66
|
+
results = await manager._execute_query(
|
|
67
|
+
graph_id, query, {"qualified_name": qualified_name}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if not results:
|
|
71
|
+
return CodeSnippet(
|
|
72
|
+
qualified_name=qualified_name,
|
|
73
|
+
source_code="",
|
|
74
|
+
file_path="",
|
|
75
|
+
line_start=0,
|
|
76
|
+
line_end=0,
|
|
77
|
+
found=False,
|
|
78
|
+
error_message=f"Entity '{qualified_name}' not found in graph.",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Extract the first (and only) result
|
|
82
|
+
result = results[0]
|
|
83
|
+
file_path_str = result.get("path")
|
|
84
|
+
start_line = result.get("start_line", 0)
|
|
85
|
+
end_line = result.get("end_line", 0)
|
|
86
|
+
docstring = result.get("docstring")
|
|
87
|
+
|
|
88
|
+
# Check if we have all required location data
|
|
89
|
+
if not all([file_path_str, start_line, end_line]):
|
|
90
|
+
return CodeSnippet(
|
|
91
|
+
qualified_name=qualified_name,
|
|
92
|
+
source_code="",
|
|
93
|
+
file_path=file_path_str or "",
|
|
94
|
+
line_start=start_line or 0,
|
|
95
|
+
line_end=end_line or 0,
|
|
96
|
+
found=False,
|
|
97
|
+
error_message="Graph entry is missing location data. The DEFINES relationship may be missing.",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Get the repository path from the Project node
|
|
101
|
+
repo_path_query = "MATCH (p:Project) RETURN p.repo_path LIMIT 1"
|
|
102
|
+
repo_results = await manager._execute_query(graph_id, repo_path_query)
|
|
103
|
+
|
|
104
|
+
if not repo_results or not repo_results[0].get("p.repo_path"):
|
|
105
|
+
return CodeSnippet(
|
|
106
|
+
qualified_name=qualified_name,
|
|
107
|
+
source_code="",
|
|
108
|
+
file_path=file_path_str or "",
|
|
109
|
+
line_start=start_line,
|
|
110
|
+
line_end=end_line,
|
|
111
|
+
found=False,
|
|
112
|
+
error_message="Repository path not found in graph.",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
repo_path = Path(repo_results[0]["p.repo_path"])
|
|
116
|
+
|
|
117
|
+
# Read the source code from the file
|
|
118
|
+
if file_path_str is None:
|
|
119
|
+
return CodeSnippet(
|
|
120
|
+
qualified_name=qualified_name,
|
|
121
|
+
source_code="",
|
|
122
|
+
file_path="",
|
|
123
|
+
line_start=start_line,
|
|
124
|
+
line_end=end_line,
|
|
125
|
+
found=False,
|
|
126
|
+
error_message="File path is missing from graph data.",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
full_path = repo_path / file_path_str
|
|
130
|
+
|
|
131
|
+
if not full_path.exists():
|
|
132
|
+
return CodeSnippet(
|
|
133
|
+
qualified_name=qualified_name,
|
|
134
|
+
source_code="",
|
|
135
|
+
file_path=file_path_str,
|
|
136
|
+
line_start=start_line,
|
|
137
|
+
line_end=end_line,
|
|
138
|
+
found=False,
|
|
139
|
+
error_message=f"Source file not found: {full_path}",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Read the file and extract the snippet
|
|
143
|
+
try:
|
|
144
|
+
with full_path.open("r", encoding="utf-8") as f:
|
|
145
|
+
all_lines = f.readlines()
|
|
146
|
+
|
|
147
|
+
# Extract the relevant lines (1-indexed to 0-indexed)
|
|
148
|
+
snippet_lines = all_lines[start_line - 1 : end_line]
|
|
149
|
+
source_code = "".join(snippet_lines)
|
|
150
|
+
|
|
151
|
+
return CodeSnippet(
|
|
152
|
+
qualified_name=qualified_name,
|
|
153
|
+
source_code=source_code,
|
|
154
|
+
file_path=file_path_str,
|
|
155
|
+
line_start=start_line,
|
|
156
|
+
line_end=end_line,
|
|
157
|
+
docstring=docstring,
|
|
158
|
+
found=True,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.error(f"Error reading source file: {e}")
|
|
163
|
+
return CodeSnippet(
|
|
164
|
+
qualified_name=qualified_name,
|
|
165
|
+
source_code="",
|
|
166
|
+
file_path=file_path_str,
|
|
167
|
+
line_start=start_line,
|
|
168
|
+
line_end=end_line,
|
|
169
|
+
found=False,
|
|
170
|
+
error_message=f"Error reading source file: {str(e)}",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Error retrieving code: {e}", exc_info=True)
|
|
175
|
+
return CodeSnippet(
|
|
176
|
+
qualified_name=qualified_name,
|
|
177
|
+
source_code="",
|
|
178
|
+
file_path="",
|
|
179
|
+
line_start=0,
|
|
180
|
+
line_end=0,
|
|
181
|
+
found=False,
|
|
182
|
+
error_message=str(e),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def retrieve_code_by_cypher(
|
|
187
|
+
manager: "CodebaseGraphManager", graph_id: str, cypher_query: str
|
|
188
|
+
) -> list[CodeSnippet]:
|
|
189
|
+
"""Retrieve code snippets for all entities matching a Cypher query.
|
|
190
|
+
|
|
191
|
+
The query should return nodes with qualified_name property.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
manager: CodebaseGraphManager instance
|
|
195
|
+
graph_id: Graph ID to query
|
|
196
|
+
cypher_query: Cypher query that returns entities
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of CodeSnippet objects
|
|
200
|
+
"""
|
|
201
|
+
logger.info(f"Retrieving code using Cypher query: {cypher_query}")
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
# Execute the user's query
|
|
205
|
+
results = await manager._execute_query(graph_id, cypher_query)
|
|
206
|
+
|
|
207
|
+
if not results:
|
|
208
|
+
logger.info("No results from Cypher query")
|
|
209
|
+
return []
|
|
210
|
+
|
|
211
|
+
# Extract qualified names from results
|
|
212
|
+
qualified_names = set()
|
|
213
|
+
for result in results:
|
|
214
|
+
# Try different possible property names
|
|
215
|
+
for key in [
|
|
216
|
+
"qualified_name",
|
|
217
|
+
"n.qualified_name",
|
|
218
|
+
"c.qualified_name",
|
|
219
|
+
"f.qualified_name",
|
|
220
|
+
"m.qualified_name",
|
|
221
|
+
]:
|
|
222
|
+
if key in result and result[key]:
|
|
223
|
+
qualified_names.add(result[key])
|
|
224
|
+
break
|
|
225
|
+
|
|
226
|
+
if not qualified_names:
|
|
227
|
+
logger.warning("Query results don't contain qualified_name properties")
|
|
228
|
+
return []
|
|
229
|
+
|
|
230
|
+
logger.info(f"Found {len(qualified_names)} entities to retrieve code for")
|
|
231
|
+
|
|
232
|
+
# Retrieve code for each qualified name
|
|
233
|
+
snippets = []
|
|
234
|
+
for qn in qualified_names:
|
|
235
|
+
snippet = await retrieve_code_by_qualified_name(manager, graph_id, qn)
|
|
236
|
+
snippets.append(snippet)
|
|
237
|
+
|
|
238
|
+
return snippets
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(f"Error executing Cypher query: {e}", exc_info=True)
|
|
242
|
+
# Return empty list on query error
|
|
243
|
+
return []
|