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
shotgun/cli/update.py
CHANGED
|
@@ -45,7 +45,7 @@ def update(
|
|
|
45
45
|
|
|
46
46
|
This command will:
|
|
47
47
|
- Check PyPI for the latest version
|
|
48
|
-
- Detect your installation method (pipx, pip, or venv)
|
|
48
|
+
- Detect your installation method (uvx, uv-tool, pipx, pip, or venv)
|
|
49
49
|
- Perform the appropriate upgrade command
|
|
50
50
|
|
|
51
51
|
Examples:
|
|
@@ -93,6 +93,8 @@ def update(
|
|
|
93
93
|
)
|
|
94
94
|
console.print(
|
|
95
95
|
"Use --force to update anyway, or install the stable version with:\n"
|
|
96
|
+
" uv tool install shotgun-sh\n"
|
|
97
|
+
" or\n"
|
|
96
98
|
" pipx install shotgun-sh\n"
|
|
97
99
|
" or\n"
|
|
98
100
|
" pip install shotgun-sh",
|
|
@@ -134,7 +136,19 @@ def update(
|
|
|
134
136
|
console.print(f"\n[red]✗[/red] {message}", style="bold red")
|
|
135
137
|
|
|
136
138
|
# Provide manual update instructions
|
|
137
|
-
if method == "
|
|
139
|
+
if method == "uvx":
|
|
140
|
+
console.print(
|
|
141
|
+
"\n[yellow]Run uvx again to use the latest version:[/yellow]\n"
|
|
142
|
+
" uvx shotgun-sh\n"
|
|
143
|
+
"\n[yellow]Or install permanently:[/yellow]\n"
|
|
144
|
+
" uv tool install shotgun-sh"
|
|
145
|
+
)
|
|
146
|
+
elif method == "uv-tool":
|
|
147
|
+
console.print(
|
|
148
|
+
"\n[yellow]Try updating manually:[/yellow]\n"
|
|
149
|
+
" uv tool upgrade shotgun-sh"
|
|
150
|
+
)
|
|
151
|
+
elif method == "pipx":
|
|
138
152
|
console.print(
|
|
139
153
|
"\n[yellow]Try updating manually:[/yellow]\n"
|
|
140
154
|
" pipx upgrade shotgun-sh"
|
|
@@ -6,6 +6,7 @@ from enum import Enum
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any, cast
|
|
8
8
|
|
|
9
|
+
import aiofiles
|
|
9
10
|
import kuzu
|
|
10
11
|
|
|
11
12
|
from shotgun.logging_config import get_logger
|
|
@@ -301,7 +302,7 @@ class ChangeDetector:
|
|
|
301
302
|
# Direct substring match
|
|
302
303
|
return pattern in filepath
|
|
303
304
|
|
|
304
|
-
def _calculate_file_hash(self, filepath: Path) -> str:
|
|
305
|
+
async def _calculate_file_hash(self, filepath: Path) -> str:
|
|
305
306
|
"""Calculate hash of file contents.
|
|
306
307
|
|
|
307
308
|
Args:
|
|
@@ -311,8 +312,9 @@ class ChangeDetector:
|
|
|
311
312
|
SHA256 hash of file contents
|
|
312
313
|
"""
|
|
313
314
|
try:
|
|
314
|
-
with open(filepath, "rb") as f:
|
|
315
|
-
|
|
315
|
+
async with aiofiles.open(filepath, "rb") as f:
|
|
316
|
+
content = await f.read()
|
|
317
|
+
return hashlib.sha256(content).hexdigest()
|
|
316
318
|
except Exception as e:
|
|
317
319
|
logger.error(f"Failed to calculate hash for {filepath}: {e}")
|
|
318
320
|
return ""
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
+
import aiofiles
|
|
6
7
|
from pydantic import BaseModel
|
|
7
8
|
|
|
8
9
|
from shotgun.logging_config import get_logger
|
|
@@ -141,8 +142,9 @@ async def retrieve_code_by_qualified_name(
|
|
|
141
142
|
|
|
142
143
|
# Read the file and extract the snippet
|
|
143
144
|
try:
|
|
144
|
-
with
|
|
145
|
-
|
|
145
|
+
async with aiofiles.open(full_path, encoding="utf-8") as f:
|
|
146
|
+
content = await f.read()
|
|
147
|
+
all_lines = content.splitlines(keepends=True)
|
|
146
148
|
|
|
147
149
|
# Extract the relevant lines (1-indexed to 0-indexed)
|
|
148
150
|
snippet_lines = all_lines[start_line - 1 : end_line]
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"""Kuzu graph ingestor for building code knowledge graphs."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import hashlib
|
|
4
5
|
import os
|
|
5
6
|
import time
|
|
6
7
|
import uuid
|
|
7
8
|
from collections import defaultdict
|
|
9
|
+
from collections.abc import Callable
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import Any
|
|
10
12
|
|
|
13
|
+
import aiofiles
|
|
11
14
|
import kuzu
|
|
12
15
|
from tree_sitter import Node, Parser, QueryCursor
|
|
13
16
|
|
|
@@ -196,11 +199,21 @@ class Ingestor:
|
|
|
196
199
|
return True
|
|
197
200
|
return False
|
|
198
201
|
|
|
199
|
-
def flush_nodes(
|
|
200
|
-
|
|
202
|
+
def flush_nodes(
|
|
203
|
+
self,
|
|
204
|
+
progress_callback: Callable[[int, int], None] | None = None,
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Flush pending node insertions to the database.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
progress_callback: Optional callback(current, total) for progress reporting
|
|
210
|
+
"""
|
|
201
211
|
if not self.node_buffer:
|
|
202
212
|
return
|
|
203
213
|
|
|
214
|
+
total_nodes = len(self.node_buffer)
|
|
215
|
+
processed = 0
|
|
216
|
+
|
|
204
217
|
# Group nodes by label
|
|
205
218
|
nodes_by_label: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
|
206
219
|
for label, properties in self.node_buffer:
|
|
@@ -237,9 +250,18 @@ class Ingestor:
|
|
|
237
250
|
params = dict(zip(prop_names, prop_values, strict=False))
|
|
238
251
|
self.conn.execute(query, params)
|
|
239
252
|
|
|
253
|
+
# Report progress
|
|
254
|
+
processed += 1
|
|
255
|
+
if progress_callback and processed % 10 == 0:
|
|
256
|
+
progress_callback(processed, total_nodes)
|
|
257
|
+
|
|
240
258
|
except Exception as e:
|
|
241
259
|
logger.error(f"Failed to insert {label} nodes: {e}")
|
|
242
260
|
|
|
261
|
+
# Final progress report
|
|
262
|
+
if progress_callback:
|
|
263
|
+
progress_callback(total_nodes, total_nodes)
|
|
264
|
+
|
|
243
265
|
# Log node counts by type
|
|
244
266
|
node_type_counts: dict[str, int] = {}
|
|
245
267
|
for label, _ in self.node_buffer:
|
|
@@ -278,11 +300,21 @@ class Ingestor:
|
|
|
278
300
|
|
|
279
301
|
# Don't auto-flush relationships - wait for explicit flush_all() to ensure nodes exist first
|
|
280
302
|
|
|
281
|
-
def flush_relationships(
|
|
282
|
-
|
|
303
|
+
def flush_relationships(
|
|
304
|
+
self,
|
|
305
|
+
progress_callback: Callable[[int, int], None] | None = None,
|
|
306
|
+
) -> None:
|
|
307
|
+
"""Flush pending relationship insertions to the database.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
progress_callback: Optional callback(current, total) for progress reporting
|
|
311
|
+
"""
|
|
283
312
|
if not self.relationship_buffer:
|
|
284
313
|
return
|
|
285
314
|
|
|
315
|
+
total_rels = len(self.relationship_buffer)
|
|
316
|
+
processed = 0
|
|
317
|
+
|
|
286
318
|
# Group relationships by type
|
|
287
319
|
rels_by_type: dict[
|
|
288
320
|
str, list[tuple[str, str, Any, str, str, str, Any, dict[str, Any] | None]]
|
|
@@ -297,7 +329,7 @@ class Ingestor:
|
|
|
297
329
|
to_label,
|
|
298
330
|
to_key,
|
|
299
331
|
to_value,
|
|
300
|
-
|
|
332
|
+
_properties,
|
|
301
333
|
) = rel_data
|
|
302
334
|
|
|
303
335
|
# Determine actual table name
|
|
@@ -321,7 +353,7 @@ class Ingestor:
|
|
|
321
353
|
to_label,
|
|
322
354
|
to_key,
|
|
323
355
|
to_value,
|
|
324
|
-
|
|
356
|
+
_properties,
|
|
325
357
|
) = rel_data
|
|
326
358
|
|
|
327
359
|
# Build MATCH and MERGE query (use MERGE to avoid duplicate relationships)
|
|
@@ -335,6 +367,11 @@ class Ingestor:
|
|
|
335
367
|
try:
|
|
336
368
|
self.conn.execute(query, params)
|
|
337
369
|
success_count += 1
|
|
370
|
+
|
|
371
|
+
# Report progress
|
|
372
|
+
processed += 1
|
|
373
|
+
if progress_callback and processed % 10 == 0:
|
|
374
|
+
progress_callback(processed, total_rels)
|
|
338
375
|
except Exception as e:
|
|
339
376
|
logger.error(
|
|
340
377
|
f"Failed to create single relationship {table_name}: {from_label}({from_value}) -> {to_label}({to_value})"
|
|
@@ -358,6 +395,10 @@ class Ingestor:
|
|
|
358
395
|
# Don't swallow the exception - let it propagate
|
|
359
396
|
raise
|
|
360
397
|
|
|
398
|
+
# Final progress report
|
|
399
|
+
if progress_callback:
|
|
400
|
+
progress_callback(total_rels, total_rels)
|
|
401
|
+
|
|
361
402
|
# Log summary of flushed relationships
|
|
362
403
|
logger.info(
|
|
363
404
|
f"Flushed {len(self.relationship_buffer)} relationships: {relationship_counts}"
|
|
@@ -584,6 +625,9 @@ class SimpleGraphBuilder:
|
|
|
584
625
|
self.ignore_dirs = self.ignore_dirs.union(set(exclude_patterns))
|
|
585
626
|
self.progress_callback = progress_callback
|
|
586
627
|
|
|
628
|
+
# Generate unique session ID for correlating timing events in PostHog
|
|
629
|
+
self._index_session_id = str(uuid.uuid4())[:8]
|
|
630
|
+
|
|
587
631
|
# Caches
|
|
588
632
|
self.structural_elements: dict[Path, str | None] = {}
|
|
589
633
|
self.ast_cache: dict[Path, tuple[Node, str]] = {}
|
|
@@ -619,25 +663,129 @@ class SimpleGraphBuilder:
|
|
|
619
663
|
# Don't let progress callback errors crash the build
|
|
620
664
|
logger.debug(f"Progress callback error: {e}")
|
|
621
665
|
|
|
622
|
-
def
|
|
666
|
+
def _log_timing(
|
|
667
|
+
self,
|
|
668
|
+
phase: str,
|
|
669
|
+
duration: float,
|
|
670
|
+
items: int,
|
|
671
|
+
extra_props: dict[str, Any] | None = None,
|
|
672
|
+
) -> None:
|
|
673
|
+
"""Log timing data to PostHog for analysis."""
|
|
674
|
+
from shotgun.posthog_telemetry import track_event
|
|
675
|
+
|
|
676
|
+
properties: dict[str, Any] = {
|
|
677
|
+
"session_id": self._index_session_id,
|
|
678
|
+
"phase": phase,
|
|
679
|
+
"duration_seconds": round(duration, 3),
|
|
680
|
+
"item_count": items,
|
|
681
|
+
}
|
|
682
|
+
if extra_props:
|
|
683
|
+
properties.update(extra_props)
|
|
684
|
+
|
|
685
|
+
track_event("codebase_index_phase_completed", properties)
|
|
686
|
+
|
|
687
|
+
def _log_summary(
|
|
688
|
+
self,
|
|
689
|
+
total_duration: float,
|
|
690
|
+
total_files: int,
|
|
691
|
+
total_nodes: int,
|
|
692
|
+
total_relationships: int,
|
|
693
|
+
) -> None:
|
|
694
|
+
"""Log indexing summary event to PostHog."""
|
|
695
|
+
from shotgun.posthog_telemetry import track_event
|
|
696
|
+
|
|
697
|
+
track_event(
|
|
698
|
+
"codebase_index_completed",
|
|
699
|
+
{
|
|
700
|
+
"session_id": self._index_session_id,
|
|
701
|
+
"total_duration_seconds": round(total_duration, 3),
|
|
702
|
+
"total_files": total_files,
|
|
703
|
+
"total_nodes": total_nodes,
|
|
704
|
+
"total_relationships": total_relationships,
|
|
705
|
+
},
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
async def run(self) -> None:
|
|
623
709
|
"""Run the three-pass graph building process."""
|
|
624
710
|
logger.info(f"Building graph for project: {self.project_name}")
|
|
625
711
|
|
|
626
712
|
# Pass 1: Structure
|
|
627
713
|
logger.info("Pass 1: Identifying packages and folders...")
|
|
714
|
+
t0 = time.time()
|
|
628
715
|
self._identify_structure()
|
|
716
|
+
t1 = time.time()
|
|
717
|
+
self._log_timing("structure", t1 - t0, len(self.structural_elements))
|
|
629
718
|
|
|
630
719
|
# Pass 2: Definitions
|
|
631
720
|
logger.info("Pass 2: Processing files and extracting definitions...")
|
|
632
|
-
|
|
721
|
+
t2 = time.time()
|
|
722
|
+
await self._process_files()
|
|
723
|
+
t3 = time.time()
|
|
724
|
+
self._log_timing(
|
|
725
|
+
"definitions",
|
|
726
|
+
t3 - t2,
|
|
727
|
+
len(self.ast_cache),
|
|
728
|
+
{"file_count": len(self.ast_cache)},
|
|
729
|
+
)
|
|
633
730
|
|
|
634
731
|
# Pass 3: Relationships
|
|
635
732
|
logger.info("Pass 3: Processing relationships (calls, imports)...")
|
|
733
|
+
t4 = time.time()
|
|
636
734
|
self._process_relationships()
|
|
735
|
+
t5 = time.time()
|
|
736
|
+
self._log_timing("relationships", t5 - t4, len(self.ast_cache))
|
|
637
737
|
|
|
638
738
|
# Flush all pending operations
|
|
639
739
|
logger.info("Flushing all data to database...")
|
|
640
|
-
|
|
740
|
+
t6 = time.time()
|
|
741
|
+
node_count = len(self.ingestor.node_buffer)
|
|
742
|
+
|
|
743
|
+
# Create progress callback for flush_nodes
|
|
744
|
+
def node_progress(current: int, total: int) -> None:
|
|
745
|
+
self._report_progress(
|
|
746
|
+
"flush_nodes", "Flushing nodes to database", current, total
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
self.ingestor.flush_nodes(progress_callback=node_progress)
|
|
750
|
+
self._report_progress(
|
|
751
|
+
"flush_nodes", "Flushing nodes to database", node_count, node_count, True
|
|
752
|
+
)
|
|
753
|
+
t7 = time.time()
|
|
754
|
+
self._log_timing("flush_nodes", t7 - t6, node_count, {"node_count": node_count})
|
|
755
|
+
|
|
756
|
+
rel_count = len(self.ingestor.relationship_buffer)
|
|
757
|
+
|
|
758
|
+
# Create progress callback for flush_relationships
|
|
759
|
+
def rel_progress(current: int, total: int) -> None:
|
|
760
|
+
self._report_progress(
|
|
761
|
+
"flush_relationships",
|
|
762
|
+
"Flushing relationships to database",
|
|
763
|
+
current,
|
|
764
|
+
total,
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
self.ingestor.flush_relationships(progress_callback=rel_progress)
|
|
768
|
+
self._report_progress(
|
|
769
|
+
"flush_relationships",
|
|
770
|
+
"Flushing relationships to database",
|
|
771
|
+
rel_count,
|
|
772
|
+
rel_count,
|
|
773
|
+
True,
|
|
774
|
+
)
|
|
775
|
+
t8 = time.time()
|
|
776
|
+
self._log_timing(
|
|
777
|
+
"flush_relationships", t8 - t7, rel_count, {"relationship_count": rel_count}
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
# Track summary event with totals (no PII - only numeric metadata)
|
|
781
|
+
total_duration = t8 - t0
|
|
782
|
+
self._log_summary(
|
|
783
|
+
total_duration=total_duration,
|
|
784
|
+
total_files=len(self.ast_cache),
|
|
785
|
+
total_nodes=node_count,
|
|
786
|
+
total_relationships=rel_count,
|
|
787
|
+
)
|
|
788
|
+
|
|
641
789
|
logger.info("Graph building complete!")
|
|
642
790
|
|
|
643
791
|
def _identify_structure(self) -> None:
|
|
@@ -771,7 +919,7 @@ class SimpleGraphBuilder:
|
|
|
771
919
|
phase_complete=True,
|
|
772
920
|
)
|
|
773
921
|
|
|
774
|
-
def _process_files(self) -> None:
|
|
922
|
+
async def _process_files(self) -> None:
|
|
775
923
|
"""Second pass: Process files and extract definitions."""
|
|
776
924
|
# First pass: Count total files
|
|
777
925
|
total_files = 0
|
|
@@ -807,7 +955,7 @@ class SimpleGraphBuilder:
|
|
|
807
955
|
lang_config = get_language_config(ext)
|
|
808
956
|
|
|
809
957
|
if lang_config and lang_config.name in self.parsers:
|
|
810
|
-
self._process_single_file(filepath, lang_config.name)
|
|
958
|
+
await self._process_single_file(filepath, lang_config.name)
|
|
811
959
|
file_count += 1
|
|
812
960
|
|
|
813
961
|
# Report progress after each file
|
|
@@ -832,7 +980,7 @@ class SimpleGraphBuilder:
|
|
|
832
980
|
phase_complete=True,
|
|
833
981
|
)
|
|
834
982
|
|
|
835
|
-
def _process_single_file(self, filepath: Path, language: str) -> None:
|
|
983
|
+
async def _process_single_file(self, filepath: Path, language: str) -> None:
|
|
836
984
|
"""Process a single file."""
|
|
837
985
|
relative_path = filepath.relative_to(self.repo_path)
|
|
838
986
|
relative_path_str = str(relative_path).replace(os.sep, "/")
|
|
@@ -873,8 +1021,8 @@ class SimpleGraphBuilder:
|
|
|
873
1021
|
|
|
874
1022
|
# Parse file
|
|
875
1023
|
try:
|
|
876
|
-
with open(filepath, "rb") as f:
|
|
877
|
-
content = f.read()
|
|
1024
|
+
async with aiofiles.open(filepath, "rb") as f:
|
|
1025
|
+
content = await f.read()
|
|
878
1026
|
|
|
879
1027
|
parser = self.parsers[language]
|
|
880
1028
|
tree = parser.parse(content)
|
|
@@ -1636,7 +1784,7 @@ class CodebaseIngestor:
|
|
|
1636
1784
|
)
|
|
1637
1785
|
if self.project_name:
|
|
1638
1786
|
builder.project_name = self.project_name
|
|
1639
|
-
builder.run()
|
|
1787
|
+
asyncio.run(builder.run())
|
|
1640
1788
|
|
|
1641
1789
|
logger.info(f"Graph successfully created at: {self.db_path}")
|
|
1642
1790
|
|
shotgun/codebase/core/manager.py
CHANGED
|
@@ -371,7 +371,16 @@ class CodebaseGraphManager:
|
|
|
371
371
|
)
|
|
372
372
|
import shutil
|
|
373
373
|
|
|
374
|
-
|
|
374
|
+
# Handle both files and directories (kuzu v0.11.2+ uses files)
|
|
375
|
+
if graph_path.is_file():
|
|
376
|
+
graph_path.unlink() # Delete file
|
|
377
|
+
# Also delete WAL file if it exists
|
|
378
|
+
wal_path = graph_path.with_suffix(graph_path.suffix + ".wal")
|
|
379
|
+
if wal_path.exists():
|
|
380
|
+
wal_path.unlink()
|
|
381
|
+
logger.debug(f"Deleted WAL file: {wal_path}")
|
|
382
|
+
else:
|
|
383
|
+
shutil.rmtree(graph_path) # Delete directory
|
|
375
384
|
|
|
376
385
|
# Import the builder from local core module
|
|
377
386
|
from shotgun.codebase.core import CodebaseIngestor
|
|
@@ -760,7 +769,7 @@ class CodebaseGraphManager:
|
|
|
760
769
|
|
|
761
770
|
lang_config = get_language_config(full_path.suffix)
|
|
762
771
|
if lang_config and lang_config.name in parsers:
|
|
763
|
-
builder._process_single_file(full_path, lang_config.name)
|
|
772
|
+
await builder._process_single_file(full_path, lang_config.name)
|
|
764
773
|
stats["nodes_modified"] += 1 # Approximate
|
|
765
774
|
|
|
766
775
|
# Process additions
|
|
@@ -775,7 +784,7 @@ class CodebaseGraphManager:
|
|
|
775
784
|
|
|
776
785
|
lang_config = get_language_config(full_path.suffix)
|
|
777
786
|
if lang_config and lang_config.name in parsers:
|
|
778
|
-
builder._process_single_file(full_path, lang_config.name)
|
|
787
|
+
await builder._process_single_file(full_path, lang_config.name)
|
|
779
788
|
stats["nodes_added"] += 1 # Approximate
|
|
780
789
|
|
|
781
790
|
# Flush all pending operations
|
|
@@ -1742,7 +1751,7 @@ class CodebaseGraphManager:
|
|
|
1742
1751
|
)
|
|
1743
1752
|
|
|
1744
1753
|
# Build the graph
|
|
1745
|
-
builder.run()
|
|
1754
|
+
asyncio.run(builder.run())
|
|
1746
1755
|
|
|
1747
1756
|
# Run build in thread pool
|
|
1748
1757
|
await anyio.to_thread.run_sync(_build_graph)
|
|
@@ -34,7 +34,7 @@ async def llm_cypher_prompt(
|
|
|
34
34
|
Returns:
|
|
35
35
|
CypherGenerationResponse with cypher_query, can_generate flag, and reason if not
|
|
36
36
|
"""
|
|
37
|
-
model_config = get_provider_model()
|
|
37
|
+
model_config = await get_provider_model()
|
|
38
38
|
|
|
39
39
|
# Create an agent with structured output for Cypher generation
|
|
40
40
|
cypher_agent = Agent(
|
shotgun/codebase/models.py
CHANGED
|
@@ -29,6 +29,8 @@ class ProgressPhase(StrEnum):
|
|
|
29
29
|
STRUCTURE = "structure" # Identifying packages and folders
|
|
30
30
|
DEFINITIONS = "definitions" # Processing files and extracting definitions
|
|
31
31
|
RELATIONSHIPS = "relationships" # Processing relationships (calls, imports)
|
|
32
|
+
FLUSH_NODES = "flush_nodes" # Flushing nodes to database
|
|
33
|
+
FLUSH_RELATIONSHIPS = "flush_relationships" # Flushing relationships to database
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
class IndexProgress(BaseModel):
|