shotgun-sh 0.1.12.dev3__tar.gz → 0.1.13.dev1__tar.gz
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_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/PKG-INFO +1 -1
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/pyproject.toml +1 -1
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/agent_manager.py +30 -1
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/codebase/commands.py +71 -2
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/core/ingestor.py +113 -4
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/core/manager.py +164 -3
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/models.py +25 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/service.py +14 -2
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +13 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/sdk/codebase.py +11 -2
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/app.py +20 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/screens/chat.py +80 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/screens/chat_screen/history.py +84 -12
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/.gitignore +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/LICENSE +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/README.md +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/hatch_build.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/common.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/config/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/config/constants.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/config/manager.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/config/models.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/config/provider.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/conversation_history.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/conversation_manager.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/export.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/history/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/history/compaction.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/history/constants.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/history/context_extraction.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/history/history_building.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/history/history_processors.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/history/message_utils.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/history/token_counting.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/history/token_estimation.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/messages.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/models.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/plan.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/research.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/specify.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tasks.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/codebase/models.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/file_management.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/user_interaction.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/web_search/openai.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/agents/tools/web_search/utils.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/build_constants.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/codebase/models.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/config.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/export.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/models.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/plan.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/research.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/specify.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/tasks.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/update.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/cli/utils.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/core/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/core/change_detector.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/core/code_retrieval.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/core/cypher_models.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/core/language_config.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/core/nl_query.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/codebase/core/parser_loader.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/logging_config.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/main.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/posthog_telemetry.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/export.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/plan.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/research.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/specify.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/agents/tasks.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/history/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/history/summarization.j2 +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/prompts/loader.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/py.typed +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/sdk/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/sdk/exceptions.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/sdk/models.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/sdk/services.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/sentry_telemetry.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/telemetry.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/commands/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/components/prompt_input.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/components/spinner.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/components/splash.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/components/vertical_tail.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/filtered_codebase_service.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/screens/chat.tcss +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/screens/directory_setup.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/screens/provider_config.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/screens/splash.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/styles.tcss +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/utils/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/tui/utils/mode_progress.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/utils/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/utils/env_utils.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/utils/file_system_utils.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/utils/source_detection.py +0 -0
- {shotgun_sh-0.1.12.dev3 → shotgun_sh-0.1.13.dev1}/src/shotgun/utils/update_checker.py +0 -0
|
@@ -31,6 +31,7 @@ from pydantic_ai.messages import (
|
|
|
31
31
|
SystemPromptPart,
|
|
32
32
|
ToolCallPart,
|
|
33
33
|
ToolCallPartDelta,
|
|
34
|
+
ToolReturnPart,
|
|
34
35
|
)
|
|
35
36
|
from textual.message import Message
|
|
36
37
|
from textual.widget import Widget
|
|
@@ -44,7 +45,7 @@ from shotgun.utils.source_detection import detect_source
|
|
|
44
45
|
from .export import create_export_agent
|
|
45
46
|
from .history.compaction import apply_persistent_compaction
|
|
46
47
|
from .messages import AgentSystemPrompt
|
|
47
|
-
from .models import AgentDeps, AgentRuntimeOptions
|
|
48
|
+
from .models import AgentDeps, AgentRuntimeOptions, UserAnswer
|
|
48
49
|
from .plan import create_plan_agent
|
|
49
50
|
from .research import create_research_agent
|
|
50
51
|
from .specify import create_specify_agent
|
|
@@ -272,6 +273,8 @@ class AgentManager(Widget):
|
|
|
272
273
|
# Use merged deps (shared state + agent-specific system prompt) if not provided
|
|
273
274
|
if deps is None:
|
|
274
275
|
deps = self._create_merged_deps(self._current_agent_type)
|
|
276
|
+
if not deferred_tool_results:
|
|
277
|
+
self.ensure_agent_canecelled_safely()
|
|
275
278
|
|
|
276
279
|
# Ensure deps is not None
|
|
277
280
|
if deps is None:
|
|
@@ -674,6 +677,32 @@ class AgentManager(Widget):
|
|
|
674
677
|
self.ui_message_history.append(message)
|
|
675
678
|
self._post_messages_updated()
|
|
676
679
|
|
|
680
|
+
def ensure_agent_canecelled_safely(self) -> None:
|
|
681
|
+
if not self.message_history:
|
|
682
|
+
return
|
|
683
|
+
self.last_response = self.message_history[-1]
|
|
684
|
+
## we're searching for unanswered ask_user parts
|
|
685
|
+
found_tool = None
|
|
686
|
+
for part in self.message_history[-1].parts:
|
|
687
|
+
if isinstance(part, ToolCallPart) and part.tool_name == "ask_user":
|
|
688
|
+
found_tool = part
|
|
689
|
+
break
|
|
690
|
+
if not found_tool:
|
|
691
|
+
return
|
|
692
|
+
tool_result = ModelRequest(
|
|
693
|
+
parts=[
|
|
694
|
+
ToolReturnPart(
|
|
695
|
+
tool_call_id=found_tool.tool_call_id,
|
|
696
|
+
tool_name=found_tool.tool_name,
|
|
697
|
+
content=UserAnswer(
|
|
698
|
+
answer="⚠️ Operation cancelled by user",
|
|
699
|
+
tool_call_id=found_tool.tool_call_id,
|
|
700
|
+
),
|
|
701
|
+
)
|
|
702
|
+
]
|
|
703
|
+
)
|
|
704
|
+
self.message_history.append(tool_result)
|
|
705
|
+
|
|
677
706
|
|
|
678
707
|
# Re-export AgentType for backward compatibility
|
|
679
708
|
__all__ = [
|
|
@@ -6,8 +6,21 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Annotated
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.progress import (
|
|
11
|
+
BarColumn,
|
|
12
|
+
Progress,
|
|
13
|
+
SpinnerColumn,
|
|
14
|
+
TaskProgressColumn,
|
|
15
|
+
TextColumn,
|
|
16
|
+
TimeElapsedColumn,
|
|
17
|
+
)
|
|
9
18
|
|
|
10
|
-
from shotgun.codebase.models import
|
|
19
|
+
from shotgun.codebase.models import (
|
|
20
|
+
CodebaseGraph,
|
|
21
|
+
IndexProgress,
|
|
22
|
+
QueryType,
|
|
23
|
+
)
|
|
11
24
|
from shotgun.logging_config import get_logger
|
|
12
25
|
from shotgun.sdk.codebase import CodebaseSDK
|
|
13
26
|
from shotgun.sdk.exceptions import CodebaseNotFoundError, InvalidPathError
|
|
@@ -59,10 +72,66 @@ def index(
|
|
|
59
72
|
) -> None:
|
|
60
73
|
"""Index a new codebase."""
|
|
61
74
|
sdk = CodebaseSDK()
|
|
75
|
+
console = Console()
|
|
76
|
+
|
|
77
|
+
# Create progress display
|
|
78
|
+
progress = Progress(
|
|
79
|
+
SpinnerColumn(),
|
|
80
|
+
TextColumn("[bold blue]{task.description}"),
|
|
81
|
+
BarColumn(),
|
|
82
|
+
TaskProgressColumn(),
|
|
83
|
+
TimeElapsedColumn(),
|
|
84
|
+
console=console,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Track tasks by phase
|
|
88
|
+
tasks = {}
|
|
89
|
+
|
|
90
|
+
def progress_callback(progress_info: IndexProgress) -> None:
|
|
91
|
+
"""Update progress display based on indexing phase."""
|
|
92
|
+
phase = progress_info.phase
|
|
93
|
+
|
|
94
|
+
# Create task if it doesn't exist
|
|
95
|
+
if phase not in tasks:
|
|
96
|
+
if progress_info.total is not None:
|
|
97
|
+
tasks[phase] = progress.add_task(
|
|
98
|
+
progress_info.phase_name, total=progress_info.total
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
# Indeterminate progress (spinner only)
|
|
102
|
+
tasks[phase] = progress.add_task(progress_info.phase_name, total=None)
|
|
103
|
+
|
|
104
|
+
task_id = tasks[phase]
|
|
105
|
+
|
|
106
|
+
# Update task
|
|
107
|
+
if progress_info.total is not None:
|
|
108
|
+
progress.update(
|
|
109
|
+
task_id,
|
|
110
|
+
completed=progress_info.current,
|
|
111
|
+
total=progress_info.total,
|
|
112
|
+
description=f"[bold blue]{progress_info.phase_name}",
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
# Just update description for indeterminate tasks
|
|
116
|
+
progress.update(
|
|
117
|
+
task_id,
|
|
118
|
+
description=f"[bold blue]{progress_info.phase_name} ({progress_info.current} items)",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Mark as complete if phase is done
|
|
122
|
+
if progress_info.phase_complete:
|
|
123
|
+
if progress_info.total is not None:
|
|
124
|
+
progress.update(task_id, completed=progress_info.total)
|
|
62
125
|
|
|
63
126
|
try:
|
|
64
127
|
repo_path = Path(path)
|
|
65
|
-
|
|
128
|
+
|
|
129
|
+
# Run indexing with progress display
|
|
130
|
+
with progress:
|
|
131
|
+
result = asyncio.run(
|
|
132
|
+
sdk.index_codebase(repo_path, name, progress_callback=progress_callback)
|
|
133
|
+
)
|
|
134
|
+
|
|
66
135
|
output_result(result, format_type)
|
|
67
136
|
except InvalidPathError as e:
|
|
68
137
|
error_result = ErrorResult(error_message=str(e))
|
|
@@ -535,6 +535,7 @@ class SimpleGraphBuilder:
|
|
|
535
535
|
parsers: dict[str, Parser],
|
|
536
536
|
queries: dict[str, Any],
|
|
537
537
|
exclude_patterns: list[str] | None = None,
|
|
538
|
+
progress_callback: Any | None = None,
|
|
538
539
|
):
|
|
539
540
|
self.ingestor = ingestor
|
|
540
541
|
self.repo_path = repo_path
|
|
@@ -544,6 +545,7 @@ class SimpleGraphBuilder:
|
|
|
544
545
|
self.ignore_dirs = IGNORE_PATTERNS
|
|
545
546
|
if exclude_patterns:
|
|
546
547
|
self.ignore_dirs = self.ignore_dirs.union(set(exclude_patterns))
|
|
548
|
+
self.progress_callback = progress_callback
|
|
547
549
|
|
|
548
550
|
# Caches
|
|
549
551
|
self.structural_elements: dict[Path, str | None] = {}
|
|
@@ -552,6 +554,34 @@ class SimpleGraphBuilder:
|
|
|
552
554
|
self.simple_name_lookup: dict[str, set[str]] = defaultdict(set)
|
|
553
555
|
self.class_inheritance: dict[str, list[str]] = {} # class_qn -> [parent_qns]
|
|
554
556
|
|
|
557
|
+
def _report_progress(
|
|
558
|
+
self,
|
|
559
|
+
phase: str,
|
|
560
|
+
phase_name: str,
|
|
561
|
+
current: int,
|
|
562
|
+
total: int | None = None,
|
|
563
|
+
phase_complete: bool = False,
|
|
564
|
+
) -> None:
|
|
565
|
+
"""Report progress via callback if available."""
|
|
566
|
+
if not self.progress_callback:
|
|
567
|
+
return
|
|
568
|
+
|
|
569
|
+
try:
|
|
570
|
+
# Import here to avoid circular dependency
|
|
571
|
+
from shotgun.codebase.models import IndexProgress, ProgressPhase
|
|
572
|
+
|
|
573
|
+
progress = IndexProgress(
|
|
574
|
+
phase=ProgressPhase(phase),
|
|
575
|
+
phase_name=phase_name,
|
|
576
|
+
current=current,
|
|
577
|
+
total=total,
|
|
578
|
+
phase_complete=phase_complete,
|
|
579
|
+
)
|
|
580
|
+
self.progress_callback(progress)
|
|
581
|
+
except Exception as e:
|
|
582
|
+
# Don't let progress callback errors crash the build
|
|
583
|
+
logger.debug(f"Progress callback error: {e}")
|
|
584
|
+
|
|
555
585
|
def run(self) -> None:
|
|
556
586
|
"""Run the three-pass graph building process."""
|
|
557
587
|
logger.info(f"Building graph for project: {self.project_name}")
|
|
@@ -575,6 +605,7 @@ class SimpleGraphBuilder:
|
|
|
575
605
|
|
|
576
606
|
def _identify_structure(self) -> None:
|
|
577
607
|
"""First pass: Walk directory to find packages and folders."""
|
|
608
|
+
dir_count = 0
|
|
578
609
|
for root_str, dirs, _ in os.walk(self.repo_path, topdown=True):
|
|
579
610
|
dirs[:] = [d for d in dirs if d not in self.ignore_dirs]
|
|
580
611
|
root = Path(root_str)
|
|
@@ -584,6 +615,13 @@ class SimpleGraphBuilder:
|
|
|
584
615
|
if root == self.repo_path:
|
|
585
616
|
continue
|
|
586
617
|
|
|
618
|
+
dir_count += 1
|
|
619
|
+
# Report progress every 10 directories
|
|
620
|
+
if dir_count % 10 == 0:
|
|
621
|
+
self._report_progress(
|
|
622
|
+
"structure", "Identifying packages and folders", dir_count
|
|
623
|
+
)
|
|
624
|
+
|
|
587
625
|
parent_rel_path = relative_root.parent
|
|
588
626
|
parent_container_qn = self.structural_elements.get(parent_rel_path)
|
|
589
627
|
|
|
@@ -686,8 +724,34 @@ class SimpleGraphBuilder:
|
|
|
686
724
|
|
|
687
725
|
self.structural_elements[relative_root] = None
|
|
688
726
|
|
|
727
|
+
# Report phase completion
|
|
728
|
+
self._report_progress(
|
|
729
|
+
"structure",
|
|
730
|
+
"Identifying packages and folders",
|
|
731
|
+
dir_count,
|
|
732
|
+
phase_complete=True,
|
|
733
|
+
)
|
|
734
|
+
|
|
689
735
|
def _process_files(self) -> None:
|
|
690
736
|
"""Second pass: Process files and extract definitions."""
|
|
737
|
+
# First pass: Count total files
|
|
738
|
+
total_files = 0
|
|
739
|
+
for root_str, _, files in os.walk(self.repo_path):
|
|
740
|
+
root = Path(root_str)
|
|
741
|
+
|
|
742
|
+
# Skip ignored directories
|
|
743
|
+
if any(part in self.ignore_dirs for part in root.parts):
|
|
744
|
+
continue
|
|
745
|
+
|
|
746
|
+
for filename in files:
|
|
747
|
+
filepath = root / filename
|
|
748
|
+
ext = filepath.suffix
|
|
749
|
+
lang_config = get_language_config(ext)
|
|
750
|
+
|
|
751
|
+
if lang_config and lang_config.name in self.parsers:
|
|
752
|
+
total_files += 1
|
|
753
|
+
|
|
754
|
+
# Second pass: Process files with progress reporting
|
|
691
755
|
file_count = 0
|
|
692
756
|
for root_str, _, files in os.walk(self.repo_path):
|
|
693
757
|
root = Path(root_str)
|
|
@@ -707,10 +771,27 @@ class SimpleGraphBuilder:
|
|
|
707
771
|
self._process_single_file(filepath, lang_config.name)
|
|
708
772
|
file_count += 1
|
|
709
773
|
|
|
774
|
+
# Report progress after each file
|
|
775
|
+
self._report_progress(
|
|
776
|
+
"definitions",
|
|
777
|
+
"Processing files and extracting definitions",
|
|
778
|
+
file_count,
|
|
779
|
+
total_files,
|
|
780
|
+
)
|
|
781
|
+
|
|
710
782
|
if file_count % 100 == 0:
|
|
711
|
-
logger.info(f" Processed {file_count} files...")
|
|
783
|
+
logger.info(f" Processed {file_count}/{total_files} files...")
|
|
784
|
+
|
|
785
|
+
logger.info(f" Total files processed: {file_count}/{total_files}")
|
|
712
786
|
|
|
713
|
-
|
|
787
|
+
# Report phase completion
|
|
788
|
+
self._report_progress(
|
|
789
|
+
"definitions",
|
|
790
|
+
"Processing files and extracting definitions",
|
|
791
|
+
file_count,
|
|
792
|
+
total_files,
|
|
793
|
+
phase_complete=True,
|
|
794
|
+
)
|
|
714
795
|
|
|
715
796
|
def _process_single_file(self, filepath: Path, language: str) -> None:
|
|
716
797
|
"""Process a single file."""
|
|
@@ -1143,7 +1224,8 @@ class SimpleGraphBuilder:
|
|
|
1143
1224
|
self._process_inheritance()
|
|
1144
1225
|
|
|
1145
1226
|
# Then process function calls
|
|
1146
|
-
|
|
1227
|
+
total_files = len(self.ast_cache)
|
|
1228
|
+
logger.info(f"Processing function calls for {total_files} files...")
|
|
1147
1229
|
logger.info(f"Function registry has {len(self.function_registry)} entries")
|
|
1148
1230
|
logger.info(
|
|
1149
1231
|
f"Simple name lookup has {len(self.simple_name_lookup)} unique names"
|
|
@@ -1157,10 +1239,29 @@ class SimpleGraphBuilder:
|
|
|
1157
1239
|
f" Example: '{name}' -> {list(self.simple_name_lookup[name])[:3]}"
|
|
1158
1240
|
)
|
|
1159
1241
|
|
|
1242
|
+
file_count = 0
|
|
1160
1243
|
for filepath, (root_node, language) in self.ast_cache.items():
|
|
1161
1244
|
self._process_calls(filepath, root_node, language)
|
|
1162
1245
|
# NOTE: Add import processing. wtf does this mean?
|
|
1163
1246
|
|
|
1247
|
+
file_count += 1
|
|
1248
|
+
# Report progress after each file
|
|
1249
|
+
self._report_progress(
|
|
1250
|
+
"relationships",
|
|
1251
|
+
"Processing relationships (calls, imports)",
|
|
1252
|
+
file_count,
|
|
1253
|
+
total_files,
|
|
1254
|
+
)
|
|
1255
|
+
|
|
1256
|
+
# Report phase completion
|
|
1257
|
+
self._report_progress(
|
|
1258
|
+
"relationships",
|
|
1259
|
+
"Processing relationships (calls, imports)",
|
|
1260
|
+
file_count,
|
|
1261
|
+
total_files,
|
|
1262
|
+
phase_complete=True,
|
|
1263
|
+
)
|
|
1264
|
+
|
|
1164
1265
|
def _process_inheritance(self) -> None:
|
|
1165
1266
|
"""Process inheritance relationships between classes."""
|
|
1166
1267
|
logger.info("Processing inheritance relationships...")
|
|
@@ -1444,6 +1545,7 @@ class CodebaseIngestor:
|
|
|
1444
1545
|
db_path: str,
|
|
1445
1546
|
project_name: str | None = None,
|
|
1446
1547
|
exclude_patterns: list[str] | None = None,
|
|
1548
|
+
progress_callback: Any | None = None,
|
|
1447
1549
|
):
|
|
1448
1550
|
"""Initialize the ingestor.
|
|
1449
1551
|
|
|
@@ -1451,10 +1553,12 @@ class CodebaseIngestor:
|
|
|
1451
1553
|
db_path: Path to Kuzu database
|
|
1452
1554
|
project_name: Optional project name
|
|
1453
1555
|
exclude_patterns: Patterns to exclude from processing
|
|
1556
|
+
progress_callback: Optional callback for progress reporting
|
|
1454
1557
|
"""
|
|
1455
1558
|
self.db_path = Path(db_path)
|
|
1456
1559
|
self.project_name = project_name
|
|
1457
1560
|
self.exclude_patterns = exclude_patterns or []
|
|
1561
|
+
self.progress_callback = progress_callback
|
|
1458
1562
|
|
|
1459
1563
|
def build_graph_from_directory(self, repo_path: str) -> None:
|
|
1460
1564
|
"""Build a code knowledge graph from a directory.
|
|
@@ -1484,7 +1588,12 @@ class CodebaseIngestor:
|
|
|
1484
1588
|
|
|
1485
1589
|
# Build graph
|
|
1486
1590
|
builder = SimpleGraphBuilder(
|
|
1487
|
-
ingestor,
|
|
1591
|
+
ingestor,
|
|
1592
|
+
repo_path_obj,
|
|
1593
|
+
parsers,
|
|
1594
|
+
queries,
|
|
1595
|
+
self.exclude_patterns,
|
|
1596
|
+
self.progress_callback,
|
|
1488
1597
|
)
|
|
1489
1598
|
if self.project_name:
|
|
1490
1599
|
builder.project_name = self.project_name
|
|
@@ -329,6 +329,7 @@ class CodebaseGraphManager:
|
|
|
329
329
|
languages: list[str] | None = None,
|
|
330
330
|
exclude_patterns: list[str] | None = None,
|
|
331
331
|
indexed_from_cwd: str | None = None,
|
|
332
|
+
progress_callback: Any | None = None,
|
|
332
333
|
) -> CodebaseGraph:
|
|
333
334
|
"""Build a new code knowledge graph.
|
|
334
335
|
|
|
@@ -337,6 +338,7 @@ class CodebaseGraphManager:
|
|
|
337
338
|
name: Optional human-readable name
|
|
338
339
|
languages: Languages to parse (default: all supported)
|
|
339
340
|
exclude_patterns: Patterns to exclude
|
|
341
|
+
progress_callback: Optional callback for progress reporting
|
|
340
342
|
|
|
341
343
|
Returns:
|
|
342
344
|
Created graph metadata
|
|
@@ -353,7 +355,19 @@ class CodebaseGraphManager:
|
|
|
353
355
|
|
|
354
356
|
# Check if graph already exists
|
|
355
357
|
if graph_path.exists():
|
|
356
|
-
|
|
358
|
+
# Verify it's not corrupted by checking if we can load the Project node
|
|
359
|
+
existing_graph = await self.get_graph(graph_id)
|
|
360
|
+
if existing_graph:
|
|
361
|
+
# Valid existing graph
|
|
362
|
+
raise CodebaseAlreadyIndexedError(repo_path)
|
|
363
|
+
else:
|
|
364
|
+
# Corrupted database - remove and re-index
|
|
365
|
+
logger.warning(
|
|
366
|
+
f"Found corrupted database at {graph_path}, removing for re-indexing..."
|
|
367
|
+
)
|
|
368
|
+
import shutil
|
|
369
|
+
|
|
370
|
+
shutil.rmtree(graph_path)
|
|
357
371
|
|
|
358
372
|
# Import the builder from local core module
|
|
359
373
|
from shotgun.codebase.core import CodebaseIngestor
|
|
@@ -379,6 +393,7 @@ class CodebaseGraphManager:
|
|
|
379
393
|
db_path=str(graph_path),
|
|
380
394
|
project_name=name,
|
|
381
395
|
exclude_patterns=exclude_patterns or [],
|
|
396
|
+
progress_callback=progress_callback,
|
|
382
397
|
)
|
|
383
398
|
|
|
384
399
|
# Run build in thread pool
|
|
@@ -1193,6 +1208,136 @@ class CodebaseGraphManager:
|
|
|
1193
1208
|
)
|
|
1194
1209
|
return None
|
|
1195
1210
|
|
|
1211
|
+
async def cleanup_corrupted_databases(self) -> list[str]:
|
|
1212
|
+
"""Detect and remove corrupted Kuzu databases.
|
|
1213
|
+
|
|
1214
|
+
This method iterates through all .kuzu files in the storage directory,
|
|
1215
|
+
attempts to open them, and removes any that are corrupted or unreadable.
|
|
1216
|
+
|
|
1217
|
+
Returns:
|
|
1218
|
+
List of graph_ids that were removed due to corruption
|
|
1219
|
+
"""
|
|
1220
|
+
import shutil
|
|
1221
|
+
|
|
1222
|
+
removed_graphs = []
|
|
1223
|
+
|
|
1224
|
+
# Find all .kuzu files (can be files or directories)
|
|
1225
|
+
for path in self.storage_dir.glob("*.kuzu"):
|
|
1226
|
+
graph_id = path.stem
|
|
1227
|
+
|
|
1228
|
+
# If it's a plain file (not a directory), it's corrupted
|
|
1229
|
+
# Valid Kuzu databases are always directories
|
|
1230
|
+
if path.is_file():
|
|
1231
|
+
logger.warning(
|
|
1232
|
+
f"Detected corrupted database file (should be directory) at {path}, removing it"
|
|
1233
|
+
)
|
|
1234
|
+
try:
|
|
1235
|
+
await anyio.to_thread.run_sync(path.unlink)
|
|
1236
|
+
removed_graphs.append(graph_id)
|
|
1237
|
+
logger.info(f"Removed corrupted database file: {graph_id}")
|
|
1238
|
+
except Exception as e:
|
|
1239
|
+
logger.error(
|
|
1240
|
+
f"Failed to remove corrupted database file {graph_id}: {e}"
|
|
1241
|
+
)
|
|
1242
|
+
continue
|
|
1243
|
+
|
|
1244
|
+
try:
|
|
1245
|
+
# Try to open the database with a timeout to prevent hanging
|
|
1246
|
+
async def try_open_database(
|
|
1247
|
+
gid: str = graph_id, db_path: Path = path
|
|
1248
|
+
) -> bool:
|
|
1249
|
+
lock = await self._get_lock()
|
|
1250
|
+
async with lock:
|
|
1251
|
+
# Close existing connections if any
|
|
1252
|
+
if gid in self._connections:
|
|
1253
|
+
try:
|
|
1254
|
+
self._connections[gid].close()
|
|
1255
|
+
except Exception as e:
|
|
1256
|
+
logger.debug(
|
|
1257
|
+
f"Failed to close connection for {gid}: {e}"
|
|
1258
|
+
)
|
|
1259
|
+
del self._connections[gid]
|
|
1260
|
+
if gid in self._databases:
|
|
1261
|
+
try:
|
|
1262
|
+
self._databases[gid].close()
|
|
1263
|
+
except Exception as e:
|
|
1264
|
+
logger.debug(f"Failed to close database for {gid}: {e}")
|
|
1265
|
+
del self._databases[gid]
|
|
1266
|
+
|
|
1267
|
+
# Try to open the database
|
|
1268
|
+
def _open_and_query(g: str = gid, p: Path = db_path) -> bool:
|
|
1269
|
+
db = kuzu.Database(str(p))
|
|
1270
|
+
conn = kuzu.Connection(db)
|
|
1271
|
+
try:
|
|
1272
|
+
result = conn.execute(
|
|
1273
|
+
"MATCH (p:Project {graph_id: $graph_id}) RETURN p",
|
|
1274
|
+
{"graph_id": g},
|
|
1275
|
+
)
|
|
1276
|
+
has_results = (
|
|
1277
|
+
result.has_next()
|
|
1278
|
+
if hasattr(result, "has_next")
|
|
1279
|
+
else False
|
|
1280
|
+
)
|
|
1281
|
+
return has_results
|
|
1282
|
+
finally:
|
|
1283
|
+
conn.close()
|
|
1284
|
+
db.close()
|
|
1285
|
+
|
|
1286
|
+
return await anyio.to_thread.run_sync(_open_and_query)
|
|
1287
|
+
|
|
1288
|
+
# Try to open with 5 second timeout
|
|
1289
|
+
has_project = await asyncio.wait_for(try_open_database(), timeout=5.0)
|
|
1290
|
+
|
|
1291
|
+
if not has_project:
|
|
1292
|
+
# Database exists but has no Project node - consider it corrupted
|
|
1293
|
+
raise ValueError("No Project node found in database")
|
|
1294
|
+
|
|
1295
|
+
except (Exception, asyncio.TimeoutError) as e:
|
|
1296
|
+
# Database is corrupted or timed out - remove it
|
|
1297
|
+
error_type = (
|
|
1298
|
+
"timed out" if isinstance(e, asyncio.TimeoutError) else "corrupted"
|
|
1299
|
+
)
|
|
1300
|
+
logger.warning(
|
|
1301
|
+
f"Detected {error_type} database at {path}, removing it. "
|
|
1302
|
+
f"Error: {str(e) if not isinstance(e, asyncio.TimeoutError) else 'Operation timed out after 5 seconds'}"
|
|
1303
|
+
)
|
|
1304
|
+
|
|
1305
|
+
try:
|
|
1306
|
+
# Clean up any open connections
|
|
1307
|
+
lock = await self._get_lock()
|
|
1308
|
+
async with lock:
|
|
1309
|
+
if graph_id in self._connections:
|
|
1310
|
+
try:
|
|
1311
|
+
self._connections[graph_id].close()
|
|
1312
|
+
except Exception as e:
|
|
1313
|
+
logger.debug(
|
|
1314
|
+
f"Failed to close connection during cleanup for {graph_id}: {e}"
|
|
1315
|
+
)
|
|
1316
|
+
del self._connections[graph_id]
|
|
1317
|
+
if graph_id in self._databases:
|
|
1318
|
+
try:
|
|
1319
|
+
self._databases[graph_id].close()
|
|
1320
|
+
except Exception as e:
|
|
1321
|
+
logger.debug(
|
|
1322
|
+
f"Failed to close database during cleanup for {graph_id}: {e}"
|
|
1323
|
+
)
|
|
1324
|
+
del self._databases[graph_id]
|
|
1325
|
+
|
|
1326
|
+
# Remove the database (could be file or directory)
|
|
1327
|
+
if path.is_dir():
|
|
1328
|
+
await anyio.to_thread.run_sync(shutil.rmtree, path)
|
|
1329
|
+
else:
|
|
1330
|
+
await anyio.to_thread.run_sync(path.unlink)
|
|
1331
|
+
removed_graphs.append(graph_id)
|
|
1332
|
+
logger.info(f"Removed {error_type} database: {graph_id}")
|
|
1333
|
+
|
|
1334
|
+
except Exception as cleanup_error:
|
|
1335
|
+
logger.error(
|
|
1336
|
+
f"Failed to remove corrupted database {graph_id}: {cleanup_error}"
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
return removed_graphs
|
|
1340
|
+
|
|
1196
1341
|
async def list_graphs(self) -> list[CodebaseGraph]:
|
|
1197
1342
|
"""List all available graphs.
|
|
1198
1343
|
|
|
@@ -1464,6 +1609,7 @@ class CodebaseGraphManager:
|
|
|
1464
1609
|
languages: list[str] | None,
|
|
1465
1610
|
exclude_patterns: list[str] | None,
|
|
1466
1611
|
indexed_from_cwd: str | None = None,
|
|
1612
|
+
progress_callback: Any | None = None,
|
|
1467
1613
|
) -> CodebaseGraph:
|
|
1468
1614
|
"""Internal implementation of graph building (runs in background)."""
|
|
1469
1615
|
operation_id = str(uuid.uuid4())
|
|
@@ -1487,7 +1633,13 @@ class CodebaseGraphManager:
|
|
|
1487
1633
|
|
|
1488
1634
|
# Do the actual build work
|
|
1489
1635
|
graph = await self._do_build_graph(
|
|
1490
|
-
graph_id,
|
|
1636
|
+
graph_id,
|
|
1637
|
+
repo_path,
|
|
1638
|
+
name,
|
|
1639
|
+
languages,
|
|
1640
|
+
exclude_patterns,
|
|
1641
|
+
indexed_from_cwd,
|
|
1642
|
+
progress_callback,
|
|
1491
1643
|
)
|
|
1492
1644
|
|
|
1493
1645
|
# Update operation stats
|
|
@@ -1536,6 +1688,7 @@ class CodebaseGraphManager:
|
|
|
1536
1688
|
languages: list[str] | None,
|
|
1537
1689
|
exclude_patterns: list[str] | None,
|
|
1538
1690
|
indexed_from_cwd: str | None = None,
|
|
1691
|
+
progress_callback: Any | None = None,
|
|
1539
1692
|
) -> CodebaseGraph:
|
|
1540
1693
|
"""Execute the actual graph building logic (extracted from original build_graph)."""
|
|
1541
1694
|
# The database and Project node already exist from _initialize_graph_metadata
|
|
@@ -1591,6 +1744,7 @@ class CodebaseGraphManager:
|
|
|
1591
1744
|
parsers=parsers,
|
|
1592
1745
|
queries=queries,
|
|
1593
1746
|
exclude_patterns=exclude_patterns,
|
|
1747
|
+
progress_callback=progress_callback,
|
|
1594
1748
|
)
|
|
1595
1749
|
|
|
1596
1750
|
# Build the graph
|
|
@@ -1616,6 +1770,7 @@ class CodebaseGraphManager:
|
|
|
1616
1770
|
languages: list[str] | None = None,
|
|
1617
1771
|
exclude_patterns: list[str] | None = None,
|
|
1618
1772
|
indexed_from_cwd: str | None = None,
|
|
1773
|
+
progress_callback: Any | None = None,
|
|
1619
1774
|
) -> str:
|
|
1620
1775
|
"""Start building a new code knowledge graph asynchronously.
|
|
1621
1776
|
|
|
@@ -1654,7 +1809,13 @@ class CodebaseGraphManager:
|
|
|
1654
1809
|
# Start the build operation in background
|
|
1655
1810
|
task = asyncio.create_task(
|
|
1656
1811
|
self._build_graph_impl(
|
|
1657
|
-
graph_id,
|
|
1812
|
+
graph_id,
|
|
1813
|
+
repo_path,
|
|
1814
|
+
name,
|
|
1815
|
+
languages,
|
|
1816
|
+
exclude_patterns,
|
|
1817
|
+
indexed_from_cwd,
|
|
1818
|
+
progress_callback,
|
|
1658
1819
|
)
|
|
1659
1820
|
)
|
|
1660
1821
|
self._operations[graph_id] = task
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Data models for codebase service."""
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
@@ -22,6 +23,30 @@ class QueryType(str, Enum):
|
|
|
22
23
|
CYPHER = "cypher"
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
class ProgressPhase(str, Enum):
|
|
27
|
+
"""Phase of codebase indexing progress."""
|
|
28
|
+
|
|
29
|
+
STRUCTURE = "structure" # Identifying packages and folders
|
|
30
|
+
DEFINITIONS = "definitions" # Processing files and extracting definitions
|
|
31
|
+
RELATIONSHIPS = "relationships" # Processing relationships (calls, imports)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class IndexProgress(BaseModel):
|
|
35
|
+
"""Progress information for codebase indexing."""
|
|
36
|
+
|
|
37
|
+
phase: ProgressPhase = Field(..., description="Current indexing phase")
|
|
38
|
+
phase_name: str = Field(..., description="Human-readable phase name")
|
|
39
|
+
current: int = Field(..., description="Current item count")
|
|
40
|
+
total: int | None = Field(None, description="Total items (None if unknown)")
|
|
41
|
+
phase_complete: bool = Field(
|
|
42
|
+
default=False, description="Whether this phase is complete"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Type alias for progress callback function
|
|
47
|
+
ProgressCallback = Callable[[IndexProgress], None]
|
|
48
|
+
|
|
49
|
+
|
|
25
50
|
class OperationStats(BaseModel):
|
|
26
51
|
"""Statistics for a graph operation (build/update)."""
|
|
27
52
|
|