shotgun-sh 0.1.12.dev4__tar.gz → 0.1.13__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.dev4 → shotgun_sh-0.1.13}/PKG-INFO +1 -1
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/pyproject.toml +1 -1
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/agent_manager.py +30 -1
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/build_constants.py +2 -2
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/codebase/commands.py +71 -2
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/core/ingestor.py +113 -4
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/core/manager.py +151 -2
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/models.py +25 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/service.py +10 -2
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +13 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/sdk/codebase.py +11 -2
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/app.py +20 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/screens/chat.py +80 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/.gitignore +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/LICENSE +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/README.md +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/hatch_build.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/common.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/config/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/config/constants.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/config/manager.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/config/models.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/config/provider.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/conversation_history.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/conversation_manager.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/export.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/history/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/history/compaction.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/history/constants.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/history/context_extraction.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/history/history_building.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/history/history_processors.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/history/message_utils.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/history/token_counting.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/history/token_estimation.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/messages.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/models.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/plan.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/research.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/specify.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tasks.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/codebase/models.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/file_management.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/user_interaction.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/web_search/openai.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/agents/tools/web_search/utils.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/codebase/models.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/config.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/export.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/models.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/plan.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/research.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/specify.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/tasks.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/update.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/cli/utils.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/core/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/core/change_detector.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/core/code_retrieval.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/core/cypher_models.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/core/language_config.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/core/nl_query.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/codebase/core/parser_loader.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/logging_config.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/main.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/posthog_telemetry.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/export.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/plan.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/research.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/specify.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/agents/tasks.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/history/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/history/summarization.j2 +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/prompts/loader.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/py.typed +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/sdk/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/sdk/exceptions.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/sdk/models.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/sdk/services.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/sentry_telemetry.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/telemetry.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/commands/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/components/prompt_input.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/components/spinner.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/components/splash.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/components/vertical_tail.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/filtered_codebase_service.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/screens/chat.tcss +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/screens/directory_setup.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/screens/provider_config.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/screens/splash.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/styles.tcss +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/utils/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/tui/utils/mode_progress.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/utils/__init__.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/utils/env_utils.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/utils/file_system_utils.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/src/shotgun/utils/source_detection.py +0 -0
- {shotgun_sh-0.1.12.dev4 → shotgun_sh-0.1.13}/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__ = [
|
|
@@ -12,8 +12,8 @@ POSTHOG_API_KEY = ''
|
|
|
12
12
|
POSTHOG_PROJECT_ID = '191396'
|
|
13
13
|
|
|
14
14
|
# Logfire configuration embedded at build time (only for dev builds)
|
|
15
|
-
LOGFIRE_ENABLED = '
|
|
16
|
-
LOGFIRE_TOKEN = '
|
|
15
|
+
LOGFIRE_ENABLED = ''
|
|
16
|
+
LOGFIRE_TOKEN = ''
|
|
17
17
|
|
|
18
18
|
# Build metadata
|
|
19
19
|
BUILD_TIME_ENV = "production" if SENTRY_DSN else "development"
|
|
@@ -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
|
|
@@ -391,6 +393,7 @@ class CodebaseGraphManager:
|
|
|
391
393
|
db_path=str(graph_path),
|
|
392
394
|
project_name=name,
|
|
393
395
|
exclude_patterns=exclude_patterns or [],
|
|
396
|
+
progress_callback=progress_callback,
|
|
394
397
|
)
|
|
395
398
|
|
|
396
399
|
# Run build in thread pool
|
|
@@ -1205,6 +1208,136 @@ class CodebaseGraphManager:
|
|
|
1205
1208
|
)
|
|
1206
1209
|
return None
|
|
1207
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
|
+
|
|
1208
1341
|
async def list_graphs(self) -> list[CodebaseGraph]:
|
|
1209
1342
|
"""List all available graphs.
|
|
1210
1343
|
|
|
@@ -1476,6 +1609,7 @@ class CodebaseGraphManager:
|
|
|
1476
1609
|
languages: list[str] | None,
|
|
1477
1610
|
exclude_patterns: list[str] | None,
|
|
1478
1611
|
indexed_from_cwd: str | None = None,
|
|
1612
|
+
progress_callback: Any | None = None,
|
|
1479
1613
|
) -> CodebaseGraph:
|
|
1480
1614
|
"""Internal implementation of graph building (runs in background)."""
|
|
1481
1615
|
operation_id = str(uuid.uuid4())
|
|
@@ -1499,7 +1633,13 @@ class CodebaseGraphManager:
|
|
|
1499
1633
|
|
|
1500
1634
|
# Do the actual build work
|
|
1501
1635
|
graph = await self._do_build_graph(
|
|
1502
|
-
graph_id,
|
|
1636
|
+
graph_id,
|
|
1637
|
+
repo_path,
|
|
1638
|
+
name,
|
|
1639
|
+
languages,
|
|
1640
|
+
exclude_patterns,
|
|
1641
|
+
indexed_from_cwd,
|
|
1642
|
+
progress_callback,
|
|
1503
1643
|
)
|
|
1504
1644
|
|
|
1505
1645
|
# Update operation stats
|
|
@@ -1548,6 +1688,7 @@ class CodebaseGraphManager:
|
|
|
1548
1688
|
languages: list[str] | None,
|
|
1549
1689
|
exclude_patterns: list[str] | None,
|
|
1550
1690
|
indexed_from_cwd: str | None = None,
|
|
1691
|
+
progress_callback: Any | None = None,
|
|
1551
1692
|
) -> CodebaseGraph:
|
|
1552
1693
|
"""Execute the actual graph building logic (extracted from original build_graph)."""
|
|
1553
1694
|
# The database and Project node already exist from _initialize_graph_metadata
|
|
@@ -1603,6 +1744,7 @@ class CodebaseGraphManager:
|
|
|
1603
1744
|
parsers=parsers,
|
|
1604
1745
|
queries=queries,
|
|
1605
1746
|
exclude_patterns=exclude_patterns,
|
|
1747
|
+
progress_callback=progress_callback,
|
|
1606
1748
|
)
|
|
1607
1749
|
|
|
1608
1750
|
# Build the graph
|
|
@@ -1628,6 +1770,7 @@ class CodebaseGraphManager:
|
|
|
1628
1770
|
languages: list[str] | None = None,
|
|
1629
1771
|
exclude_patterns: list[str] | None = None,
|
|
1630
1772
|
indexed_from_cwd: str | None = None,
|
|
1773
|
+
progress_callback: Any | None = None,
|
|
1631
1774
|
) -> str:
|
|
1632
1775
|
"""Start building a new code knowledge graph asynchronously.
|
|
1633
1776
|
|
|
@@ -1666,7 +1809,13 @@ class CodebaseGraphManager:
|
|
|
1666
1809
|
# Start the build operation in background
|
|
1667
1810
|
task = asyncio.create_task(
|
|
1668
1811
|
self._build_graph_impl(
|
|
1669
|
-
graph_id,
|
|
1812
|
+
graph_id,
|
|
1813
|
+
repo_path,
|
|
1814
|
+
name,
|
|
1815
|
+
languages,
|
|
1816
|
+
exclude_patterns,
|
|
1817
|
+
indexed_from_cwd,
|
|
1818
|
+
progress_callback,
|
|
1670
1819
|
)
|
|
1671
1820
|
)
|
|
1672
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
|
|
|
@@ -77,7 +77,11 @@ class CodebaseService:
|
|
|
77
77
|
return filtered_graphs
|
|
78
78
|
|
|
79
79
|
async def create_graph(
|
|
80
|
-
self,
|
|
80
|
+
self,
|
|
81
|
+
repo_path: str | Path,
|
|
82
|
+
name: str,
|
|
83
|
+
indexed_from_cwd: str | None = None,
|
|
84
|
+
progress_callback: Any | None = None,
|
|
81
85
|
) -> CodebaseGraph:
|
|
82
86
|
"""Create and index a new graph from a repository.
|
|
83
87
|
|
|
@@ -85,12 +89,16 @@ class CodebaseService:
|
|
|
85
89
|
repo_path: Path to the repository to index
|
|
86
90
|
name: Human-readable name for the graph
|
|
87
91
|
indexed_from_cwd: Working directory from which indexing was initiated
|
|
92
|
+
progress_callback: Optional callback for progress reporting
|
|
88
93
|
|
|
89
94
|
Returns:
|
|
90
95
|
The created CodebaseGraph
|
|
91
96
|
"""
|
|
92
97
|
return await self.manager.build_graph(
|
|
93
|
-
str(repo_path),
|
|
98
|
+
str(repo_path),
|
|
99
|
+
name,
|
|
100
|
+
indexed_from_cwd=indexed_from_cwd,
|
|
101
|
+
progress_callback=progress_callback,
|
|
94
102
|
)
|
|
95
103
|
|
|
96
104
|
async def get_graph(self, graph_id: str) -> CodebaseGraph | None:
|