shotgun-sh 0.3.3.dev1__py3-none-any.whl → 0.6.2__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 +497 -30
- shotgun/agents/cancellation.py +103 -0
- shotgun/agents/common.py +90 -77
- shotgun/agents/config/README.md +0 -1
- shotgun/agents/config/manager.py +52 -8
- shotgun/agents/config/models.py +21 -27
- shotgun/agents/config/provider.py +44 -27
- shotgun/agents/conversation/history/file_content_deduplication.py +66 -43
- shotgun/agents/conversation/history/token_counting/base.py +51 -9
- shotgun/agents/export.py +12 -13
- shotgun/agents/file_read.py +176 -0
- shotgun/agents/messages.py +15 -3
- shotgun/agents/models.py +90 -2
- shotgun/agents/plan.py +12 -13
- shotgun/agents/research.py +13 -10
- shotgun/agents/router/__init__.py +47 -0
- shotgun/agents/router/models.py +384 -0
- shotgun/agents/router/router.py +185 -0
- shotgun/agents/router/tools/__init__.py +18 -0
- shotgun/agents/router/tools/delegation_tools.py +557 -0
- shotgun/agents/router/tools/plan_tools.py +403 -0
- shotgun/agents/runner.py +17 -2
- shotgun/agents/specify.py +12 -13
- shotgun/agents/tasks.py +12 -13
- shotgun/agents/tools/__init__.py +8 -0
- shotgun/agents/tools/codebase/directory_lister.py +27 -39
- shotgun/agents/tools/codebase/file_read.py +26 -35
- shotgun/agents/tools/codebase/query_graph.py +9 -0
- shotgun/agents/tools/codebase/retrieve_code.py +9 -0
- shotgun/agents/tools/file_management.py +81 -3
- shotgun/agents/tools/file_read_tools/__init__.py +7 -0
- shotgun/agents/tools/file_read_tools/multimodal_file_read.py +167 -0
- shotgun/agents/tools/markdown_tools/__init__.py +62 -0
- shotgun/agents/tools/markdown_tools/insert_section.py +148 -0
- shotgun/agents/tools/markdown_tools/models.py +86 -0
- shotgun/agents/tools/markdown_tools/remove_section.py +114 -0
- shotgun/agents/tools/markdown_tools/replace_section.py +119 -0
- shotgun/agents/tools/markdown_tools/utils.py +453 -0
- shotgun/agents/tools/registry.py +46 -6
- shotgun/agents/tools/web_search/__init__.py +1 -2
- shotgun/agents/tools/web_search/gemini.py +1 -3
- shotgun/agents/tools/web_search/openai.py +42 -23
- shotgun/attachments/__init__.py +41 -0
- shotgun/attachments/errors.py +60 -0
- shotgun/attachments/models.py +107 -0
- shotgun/attachments/parser.py +257 -0
- shotgun/attachments/processor.py +193 -0
- shotgun/build_constants.py +4 -7
- shotgun/cli/clear.py +2 -2
- shotgun/cli/codebase/commands.py +181 -65
- shotgun/cli/compact.py +2 -2
- shotgun/cli/context.py +2 -2
- shotgun/cli/error_handler.py +2 -2
- shotgun/cli/run.py +90 -0
- shotgun/cli/spec/backup.py +2 -1
- shotgun/codebase/__init__.py +2 -0
- shotgun/codebase/benchmarks/__init__.py +35 -0
- shotgun/codebase/benchmarks/benchmark_runner.py +309 -0
- shotgun/codebase/benchmarks/exporters.py +119 -0
- shotgun/codebase/benchmarks/formatters/__init__.py +49 -0
- shotgun/codebase/benchmarks/formatters/base.py +34 -0
- shotgun/codebase/benchmarks/formatters/json_formatter.py +106 -0
- shotgun/codebase/benchmarks/formatters/markdown.py +136 -0
- shotgun/codebase/benchmarks/models.py +129 -0
- shotgun/codebase/core/__init__.py +4 -0
- shotgun/codebase/core/call_resolution.py +91 -0
- shotgun/codebase/core/change_detector.py +11 -6
- shotgun/codebase/core/errors.py +159 -0
- shotgun/codebase/core/extractors/__init__.py +23 -0
- shotgun/codebase/core/extractors/base.py +138 -0
- shotgun/codebase/core/extractors/factory.py +63 -0
- shotgun/codebase/core/extractors/go/__init__.py +7 -0
- shotgun/codebase/core/extractors/go/extractor.py +122 -0
- shotgun/codebase/core/extractors/javascript/__init__.py +7 -0
- shotgun/codebase/core/extractors/javascript/extractor.py +132 -0
- shotgun/codebase/core/extractors/protocol.py +109 -0
- shotgun/codebase/core/extractors/python/__init__.py +7 -0
- shotgun/codebase/core/extractors/python/extractor.py +141 -0
- shotgun/codebase/core/extractors/rust/__init__.py +7 -0
- shotgun/codebase/core/extractors/rust/extractor.py +139 -0
- shotgun/codebase/core/extractors/types.py +15 -0
- shotgun/codebase/core/extractors/typescript/__init__.py +7 -0
- shotgun/codebase/core/extractors/typescript/extractor.py +92 -0
- shotgun/codebase/core/gitignore.py +252 -0
- shotgun/codebase/core/ingestor.py +644 -354
- shotgun/codebase/core/kuzu_compat.py +119 -0
- shotgun/codebase/core/language_config.py +239 -0
- shotgun/codebase/core/manager.py +256 -46
- shotgun/codebase/core/metrics_collector.py +310 -0
- shotgun/codebase/core/metrics_types.py +347 -0
- shotgun/codebase/core/parallel_executor.py +424 -0
- shotgun/codebase/core/work_distributor.py +254 -0
- shotgun/codebase/core/worker.py +768 -0
- shotgun/codebase/indexing_state.py +86 -0
- shotgun/codebase/models.py +94 -0
- shotgun/codebase/service.py +13 -0
- shotgun/exceptions.py +9 -9
- shotgun/main.py +3 -16
- shotgun/posthog_telemetry.py +165 -24
- shotgun/prompts/agents/export.j2 +2 -0
- shotgun/prompts/agents/file_read.j2 +48 -0
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +19 -52
- shotgun/prompts/agents/partials/content_formatting.j2 +12 -33
- shotgun/prompts/agents/partials/interactive_mode.j2 +9 -32
- shotgun/prompts/agents/partials/router_delegation_mode.j2 +35 -0
- shotgun/prompts/agents/plan.j2 +38 -12
- shotgun/prompts/agents/research.j2 +70 -31
- shotgun/prompts/agents/router.j2 +713 -0
- shotgun/prompts/agents/specify.j2 +53 -16
- shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +14 -1
- shotgun/prompts/agents/state/system_state.j2 +24 -13
- shotgun/prompts/agents/tasks.j2 +72 -34
- shotgun/settings.py +49 -10
- shotgun/tui/app.py +154 -24
- shotgun/tui/commands/__init__.py +9 -1
- shotgun/tui/components/attachment_bar.py +87 -0
- shotgun/tui/components/mode_indicator.py +120 -25
- shotgun/tui/components/prompt_input.py +25 -28
- shotgun/tui/components/status_bar.py +14 -7
- shotgun/tui/dependencies.py +58 -8
- shotgun/tui/protocols.py +55 -0
- shotgun/tui/screens/chat/chat.tcss +24 -1
- shotgun/tui/screens/chat/chat_screen.py +1376 -213
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +8 -4
- shotgun/tui/screens/chat_screen/attachment_hint.py +40 -0
- shotgun/tui/screens/chat_screen/command_providers.py +0 -97
- shotgun/tui/screens/chat_screen/history/agent_response.py +7 -3
- shotgun/tui/screens/chat_screen/history/chat_history.py +58 -6
- shotgun/tui/screens/chat_screen/history/formatters.py +75 -15
- shotgun/tui/screens/chat_screen/history/partial_response.py +11 -1
- shotgun/tui/screens/chat_screen/history/user_question.py +25 -3
- shotgun/tui/screens/chat_screen/messages.py +219 -0
- shotgun/tui/screens/database_locked_dialog.py +219 -0
- shotgun/tui/screens/database_timeout_dialog.py +158 -0
- shotgun/tui/screens/kuzu_error_dialog.py +135 -0
- shotgun/tui/screens/model_picker.py +1 -3
- shotgun/tui/screens/models.py +11 -0
- shotgun/tui/state/processing_state.py +19 -0
- shotgun/tui/utils/mode_progress.py +20 -86
- shotgun/tui/widgets/__init__.py +2 -1
- shotgun/tui/widgets/approval_widget.py +152 -0
- shotgun/tui/widgets/cascade_confirmation_widget.py +203 -0
- shotgun/tui/widgets/plan_panel.py +129 -0
- shotgun/tui/widgets/step_checkpoint_widget.py +180 -0
- shotgun/tui/widgets/widget_coordinator.py +18 -0
- shotgun/utils/file_system_utils.py +4 -1
- {shotgun_sh-0.3.3.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/METADATA +88 -35
- shotgun_sh-0.6.2.dist-info/RECORD +291 -0
- shotgun/cli/export.py +0 -81
- shotgun/cli/plan.py +0 -73
- shotgun/cli/research.py +0 -93
- shotgun/cli/specify.py +0 -70
- shotgun/cli/tasks.py +0 -78
- shotgun/sentry_telemetry.py +0 -232
- shotgun/tui/screens/onboarding.py +0 -580
- shotgun_sh-0.3.3.dev1.dist-info/RECORD +0 -229
- {shotgun_sh-0.3.3.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.3.3.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.3.3.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/licenses/LICENSE +0 -0
shotgun/codebase/core/manager.py
CHANGED
|
@@ -5,22 +5,34 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import hashlib
|
|
7
7
|
import json
|
|
8
|
+
import shutil
|
|
8
9
|
import time
|
|
9
10
|
import uuid
|
|
10
11
|
from collections.abc import Awaitable, Callable
|
|
11
12
|
from pathlib import Path
|
|
12
|
-
from typing import Any, ClassVar
|
|
13
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
13
14
|
|
|
14
15
|
import anyio
|
|
15
|
-
import kuzu
|
|
16
16
|
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
17
17
|
from watchdog.observers import Observer
|
|
18
18
|
|
|
19
|
+
from shotgun.codebase.core.kuzu_compat import get_kuzu
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
import real_ladybug as kuzu
|
|
23
|
+
|
|
24
|
+
from shotgun.codebase.core.errors import (
|
|
25
|
+
DatabaseIssue,
|
|
26
|
+
KuzuErrorType,
|
|
27
|
+
classify_kuzu_error,
|
|
28
|
+
)
|
|
19
29
|
from shotgun.codebase.models import (
|
|
20
30
|
CodebaseGraph,
|
|
21
31
|
FileChange,
|
|
22
32
|
GraphStatus,
|
|
33
|
+
NodeLabel,
|
|
23
34
|
OperationStats,
|
|
35
|
+
RelationshipType,
|
|
24
36
|
)
|
|
25
37
|
from shotgun.logging_config import get_logger
|
|
26
38
|
|
|
@@ -190,7 +202,7 @@ class CodebaseGraphManager:
|
|
|
190
202
|
return cls._lock
|
|
191
203
|
|
|
192
204
|
@classmethod
|
|
193
|
-
def
|
|
205
|
+
def generate_graph_id(cls, repo_path: str) -> str:
|
|
194
206
|
"""Generate deterministic graph ID from repository path."""
|
|
195
207
|
normalized = str(Path(repo_path).resolve())
|
|
196
208
|
return hashlib.sha256(normalized.encode()).hexdigest()[:12]
|
|
@@ -265,7 +277,8 @@ class CodebaseGraphManager:
|
|
|
265
277
|
"""
|
|
266
278
|
graph_path = self.storage_dir / f"{graph_id}.kuzu"
|
|
267
279
|
|
|
268
|
-
# Create database and connection
|
|
280
|
+
# Create database and connection (lazy import for Windows compatibility)
|
|
281
|
+
kuzu = get_kuzu()
|
|
269
282
|
lock = await self._get_lock()
|
|
270
283
|
async with lock:
|
|
271
284
|
db = kuzu.Database(str(graph_path))
|
|
@@ -348,7 +361,7 @@ class CodebaseGraphManager:
|
|
|
348
361
|
Created graph metadata
|
|
349
362
|
"""
|
|
350
363
|
repo_path = str(Path(repo_path).resolve())
|
|
351
|
-
graph_id = self.
|
|
364
|
+
graph_id = self.generate_graph_id(repo_path)
|
|
352
365
|
|
|
353
366
|
# Use repository name as default name
|
|
354
367
|
if not name:
|
|
@@ -412,7 +425,8 @@ class CodebaseGraphManager:
|
|
|
412
425
|
# Run build in thread pool
|
|
413
426
|
await anyio.to_thread.run_sync(ingestor.build_graph_from_directory, repo_path)
|
|
414
427
|
|
|
415
|
-
# Get statistics
|
|
428
|
+
# Get statistics (lazy import for Windows compatibility)
|
|
429
|
+
kuzu = get_kuzu()
|
|
416
430
|
lock = await self._get_lock()
|
|
417
431
|
async with lock:
|
|
418
432
|
db = kuzu.Database(str(graph_path))
|
|
@@ -554,6 +568,8 @@ class CodebaseGraphManager:
|
|
|
554
568
|
"relationships_removed": 0,
|
|
555
569
|
}
|
|
556
570
|
|
|
571
|
+
# Lazy import for Windows compatibility
|
|
572
|
+
kuzu = get_kuzu()
|
|
557
573
|
lock = await self._get_lock()
|
|
558
574
|
async with lock:
|
|
559
575
|
if graph_id not in self._connections:
|
|
@@ -622,6 +638,8 @@ class CodebaseGraphManager:
|
|
|
622
638
|
languages = build_options.get("languages")
|
|
623
639
|
exclude_patterns = build_options.get("exclude_patterns")
|
|
624
640
|
|
|
641
|
+
# Lazy import for Windows compatibility
|
|
642
|
+
kuzu = get_kuzu()
|
|
625
643
|
lock = await self._get_lock()
|
|
626
644
|
async with lock:
|
|
627
645
|
if graph_id not in self._connections:
|
|
@@ -1044,6 +1062,8 @@ class CodebaseGraphManager:
|
|
|
1044
1062
|
self, graph_id: str, query: str, parameters: dict[str, Any] | None = None
|
|
1045
1063
|
) -> list[dict[str, Any]]:
|
|
1046
1064
|
"""Internal query execution with connection management."""
|
|
1065
|
+
# Lazy import for Windows compatibility
|
|
1066
|
+
kuzu = get_kuzu()
|
|
1047
1067
|
lock = await self._get_lock()
|
|
1048
1068
|
async with lock:
|
|
1049
1069
|
if graph_id not in self._connections:
|
|
@@ -1216,8 +1236,19 @@ class CodebaseGraphManager:
|
|
|
1216
1236
|
indexed_from_cwds=indexed_from_cwds,
|
|
1217
1237
|
)
|
|
1218
1238
|
except Exception as e:
|
|
1239
|
+
# Classify the error to determine if we should re-raise
|
|
1240
|
+
error_type = classify_kuzu_error(e)
|
|
1241
|
+
|
|
1242
|
+
if error_type == KuzuErrorType.LOCKED:
|
|
1243
|
+
# Don't mask lock errors - let caller handle them
|
|
1244
|
+
logger.warning(
|
|
1245
|
+
f"Database locked - graph_id: {graph_id}, error: {str(e)}"
|
|
1246
|
+
)
|
|
1247
|
+
raise
|
|
1248
|
+
|
|
1219
1249
|
logger.error(
|
|
1220
|
-
f"Failed to get graph metadata - graph_id: {graph_id},
|
|
1250
|
+
f"Failed to get graph metadata - graph_id: {graph_id}, "
|
|
1251
|
+
f"error_type: {error_type.value}, error: {str(e)}"
|
|
1221
1252
|
)
|
|
1222
1253
|
return None
|
|
1223
1254
|
|
|
@@ -1264,6 +1295,7 @@ class CodebaseGraphManager:
|
|
|
1264
1295
|
|
|
1265
1296
|
# Try to open the database
|
|
1266
1297
|
def _open_and_query(g: str = gid, p: Path = db_path) -> bool:
|
|
1298
|
+
kuzu = get_kuzu()
|
|
1267
1299
|
db = kuzu.Database(str(p))
|
|
1268
1300
|
conn = kuzu.Connection(db)
|
|
1269
1301
|
try:
|
|
@@ -1336,6 +1368,174 @@ class CodebaseGraphManager:
|
|
|
1336
1368
|
|
|
1337
1369
|
return removed_graphs
|
|
1338
1370
|
|
|
1371
|
+
async def _try_open_database(self, graph_id: str, db_path: Path) -> bool:
|
|
1372
|
+
"""Try to open a database and verify it has a Project node.
|
|
1373
|
+
|
|
1374
|
+
Args:
|
|
1375
|
+
graph_id: The graph identifier
|
|
1376
|
+
db_path: Path to the database file
|
|
1377
|
+
|
|
1378
|
+
Returns:
|
|
1379
|
+
True if database opened and has Project node, False otherwise
|
|
1380
|
+
"""
|
|
1381
|
+
lock = await self._get_lock()
|
|
1382
|
+
async with lock:
|
|
1383
|
+
# Close existing connections if any
|
|
1384
|
+
if graph_id in self._connections:
|
|
1385
|
+
try:
|
|
1386
|
+
self._connections[graph_id].close()
|
|
1387
|
+
except Exception as e:
|
|
1388
|
+
logger.debug(f"Failed to close connection for {graph_id}: {e}")
|
|
1389
|
+
del self._connections[graph_id]
|
|
1390
|
+
if graph_id in self._databases:
|
|
1391
|
+
try:
|
|
1392
|
+
self._databases[graph_id].close()
|
|
1393
|
+
except Exception as e:
|
|
1394
|
+
logger.debug(f"Failed to close database for {graph_id}: {e}")
|
|
1395
|
+
del self._databases[graph_id]
|
|
1396
|
+
|
|
1397
|
+
def _open_and_query() -> bool:
|
|
1398
|
+
kuzu = get_kuzu()
|
|
1399
|
+
db = kuzu.Database(str(db_path))
|
|
1400
|
+
conn = kuzu.Connection(db)
|
|
1401
|
+
try:
|
|
1402
|
+
result = conn.execute(
|
|
1403
|
+
"MATCH (p:Project {graph_id: $graph_id}) RETURN p",
|
|
1404
|
+
{"graph_id": graph_id},
|
|
1405
|
+
)
|
|
1406
|
+
return result.has_next() if hasattr(result, "has_next") else False
|
|
1407
|
+
finally:
|
|
1408
|
+
conn.close()
|
|
1409
|
+
db.close()
|
|
1410
|
+
|
|
1411
|
+
return await anyio.to_thread.run_sync(_open_and_query)
|
|
1412
|
+
|
|
1413
|
+
async def _check_single_database(
|
|
1414
|
+
self, graph_id: str, path: Path, timeout_seconds: float
|
|
1415
|
+
) -> DatabaseIssue | None:
|
|
1416
|
+
"""Check a single database for issues.
|
|
1417
|
+
|
|
1418
|
+
Args:
|
|
1419
|
+
graph_id: The graph identifier
|
|
1420
|
+
path: Path to the database file
|
|
1421
|
+
timeout_seconds: How long to wait for the database to respond
|
|
1422
|
+
|
|
1423
|
+
Returns:
|
|
1424
|
+
DatabaseIssue if problem found, None if database is healthy
|
|
1425
|
+
"""
|
|
1426
|
+
try:
|
|
1427
|
+
has_project = await asyncio.wait_for(
|
|
1428
|
+
self._try_open_database(graph_id, path), timeout=timeout_seconds
|
|
1429
|
+
)
|
|
1430
|
+
if not has_project:
|
|
1431
|
+
return DatabaseIssue(
|
|
1432
|
+
graph_id=graph_id,
|
|
1433
|
+
graph_path=path,
|
|
1434
|
+
error_type=KuzuErrorType.SCHEMA,
|
|
1435
|
+
message="Database has no Project node (incomplete build)",
|
|
1436
|
+
)
|
|
1437
|
+
return None
|
|
1438
|
+
|
|
1439
|
+
except asyncio.TimeoutError:
|
|
1440
|
+
return DatabaseIssue(
|
|
1441
|
+
graph_id=graph_id,
|
|
1442
|
+
graph_path=path,
|
|
1443
|
+
error_type=KuzuErrorType.TIMEOUT,
|
|
1444
|
+
message=f"Database operation timed out after {timeout_seconds}s",
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
except Exception as e:
|
|
1448
|
+
error_type = classify_kuzu_error(e)
|
|
1449
|
+
logger.debug(f"Detected {error_type.value} issue with {graph_id}: {e}")
|
|
1450
|
+
return DatabaseIssue(
|
|
1451
|
+
graph_id=graph_id,
|
|
1452
|
+
graph_path=path,
|
|
1453
|
+
error_type=error_type,
|
|
1454
|
+
message=str(e),
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1457
|
+
async def detect_database_issues(
|
|
1458
|
+
self, timeout_seconds: float = 10.0
|
|
1459
|
+
) -> list[DatabaseIssue]:
|
|
1460
|
+
"""Detect issues with Kuzu databases without deleting them.
|
|
1461
|
+
|
|
1462
|
+
This method iterates through all .kuzu files in the storage directory,
|
|
1463
|
+
attempts to open them, and returns information about any issues found.
|
|
1464
|
+
Unlike cleanup_corrupted_databases(), this method does NOT delete anything -
|
|
1465
|
+
it only detects and reports issues for the caller to handle.
|
|
1466
|
+
|
|
1467
|
+
Args:
|
|
1468
|
+
timeout_seconds: How long to wait for each database to respond.
|
|
1469
|
+
Default is 10s; use 90s for retry with large codebases.
|
|
1470
|
+
|
|
1471
|
+
Returns:
|
|
1472
|
+
List of DatabaseIssue objects describing any problems found
|
|
1473
|
+
"""
|
|
1474
|
+
issues: list[DatabaseIssue] = []
|
|
1475
|
+
|
|
1476
|
+
for path in self.storage_dir.glob("*.kuzu"):
|
|
1477
|
+
graph_id = path.stem
|
|
1478
|
+
issue = await self._check_single_database(graph_id, path, timeout_seconds)
|
|
1479
|
+
if issue:
|
|
1480
|
+
issues.append(issue)
|
|
1481
|
+
|
|
1482
|
+
return issues
|
|
1483
|
+
|
|
1484
|
+
async def delete_database(self, graph_id: str) -> bool:
|
|
1485
|
+
"""Delete a database file and its WAL file.
|
|
1486
|
+
|
|
1487
|
+
Args:
|
|
1488
|
+
graph_id: The ID of the graph to delete
|
|
1489
|
+
|
|
1490
|
+
Returns:
|
|
1491
|
+
True if deletion was successful, False otherwise
|
|
1492
|
+
"""
|
|
1493
|
+
graph_path = self.storage_dir / f"{graph_id}.kuzu"
|
|
1494
|
+
|
|
1495
|
+
try:
|
|
1496
|
+
# Clean up any open connections
|
|
1497
|
+
lock = await self._get_lock()
|
|
1498
|
+
async with lock:
|
|
1499
|
+
if graph_id in self._connections:
|
|
1500
|
+
try:
|
|
1501
|
+
self._connections[graph_id].close()
|
|
1502
|
+
except Exception as e:
|
|
1503
|
+
logger.debug(
|
|
1504
|
+
f"Failed to close connection during delete for {graph_id}: {e}"
|
|
1505
|
+
)
|
|
1506
|
+
del self._connections[graph_id]
|
|
1507
|
+
if graph_id in self._databases:
|
|
1508
|
+
try:
|
|
1509
|
+
self._databases[graph_id].close()
|
|
1510
|
+
except Exception as e:
|
|
1511
|
+
logger.debug(
|
|
1512
|
+
f"Failed to close database during delete for {graph_id}: {e}"
|
|
1513
|
+
)
|
|
1514
|
+
del self._databases[graph_id]
|
|
1515
|
+
|
|
1516
|
+
# Remove the database (could be file or directory)
|
|
1517
|
+
if graph_path.exists():
|
|
1518
|
+
if graph_path.is_dir():
|
|
1519
|
+
await anyio.to_thread.run_sync(shutil.rmtree, graph_path)
|
|
1520
|
+
else:
|
|
1521
|
+
await anyio.to_thread.run_sync(graph_path.unlink)
|
|
1522
|
+
|
|
1523
|
+
# Also delete WAL file if it exists
|
|
1524
|
+
wal_path = graph_path.with_suffix(graph_path.suffix + ".wal")
|
|
1525
|
+
if wal_path.exists():
|
|
1526
|
+
await anyio.to_thread.run_sync(wal_path.unlink)
|
|
1527
|
+
logger.debug(f"Deleted WAL file: {wal_path}")
|
|
1528
|
+
|
|
1529
|
+
logger.info(f"Deleted database: {graph_id}")
|
|
1530
|
+
return True
|
|
1531
|
+
else:
|
|
1532
|
+
logger.warning(f"Database file not found for deletion: {graph_id}")
|
|
1533
|
+
return False
|
|
1534
|
+
|
|
1535
|
+
except Exception as e:
|
|
1536
|
+
logger.error(f"Failed to delete database {graph_id}: {e}")
|
|
1537
|
+
return False
|
|
1538
|
+
|
|
1339
1539
|
async def list_graphs(self) -> list[CodebaseGraph]:
|
|
1340
1540
|
"""List all available graphs.
|
|
1341
1541
|
|
|
@@ -1477,20 +1677,20 @@ class CodebaseGraphManager:
|
|
|
1477
1677
|
Returns:
|
|
1478
1678
|
Tuple of (node_stats, relationship_stats)
|
|
1479
1679
|
"""
|
|
1480
|
-
node_stats = {}
|
|
1680
|
+
node_stats: dict[str, int] = {}
|
|
1481
1681
|
|
|
1482
|
-
# Count each node type
|
|
1682
|
+
# Count each node type (excluding ExternalPackage which is rarely needed)
|
|
1483
1683
|
node_types = [
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1684
|
+
NodeLabel.PROJECT,
|
|
1685
|
+
NodeLabel.PACKAGE,
|
|
1686
|
+
NodeLabel.MODULE,
|
|
1687
|
+
NodeLabel.CLASS,
|
|
1688
|
+
NodeLabel.FUNCTION,
|
|
1689
|
+
NodeLabel.METHOD,
|
|
1690
|
+
NodeLabel.FILE,
|
|
1691
|
+
NodeLabel.FOLDER,
|
|
1692
|
+
NodeLabel.FILE_METADATA,
|
|
1693
|
+
NodeLabel.DELETION_LOG,
|
|
1494
1694
|
]
|
|
1495
1695
|
|
|
1496
1696
|
for node_type in node_types:
|
|
@@ -1505,14 +1705,14 @@ class CodebaseGraphManager:
|
|
|
1505
1705
|
logger.debug(f"Failed to count {node_type} nodes: {e}")
|
|
1506
1706
|
|
|
1507
1707
|
# Count relationships - need to handle multiple tables for each type
|
|
1508
|
-
rel_counts = {}
|
|
1708
|
+
rel_counts: dict[str, int] = {}
|
|
1509
1709
|
|
|
1510
1710
|
# CONTAINS relationships
|
|
1511
1711
|
for prefix in [
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1712
|
+
RelationshipType.CONTAINS_PACKAGE,
|
|
1713
|
+
RelationshipType.CONTAINS_FOLDER,
|
|
1714
|
+
RelationshipType.CONTAINS_FILE,
|
|
1715
|
+
RelationshipType.CONTAINS_MODULE,
|
|
1516
1716
|
]:
|
|
1517
1717
|
count = 0
|
|
1518
1718
|
for suffix in ["", "_PKG", "_FOLDER"]:
|
|
@@ -1530,13 +1730,13 @@ class CodebaseGraphManager:
|
|
|
1530
1730
|
|
|
1531
1731
|
# Other relationships
|
|
1532
1732
|
for rel_type in [
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1733
|
+
RelationshipType.DEFINES,
|
|
1734
|
+
RelationshipType.DEFINES_FUNC,
|
|
1735
|
+
RelationshipType.DEFINES_METHOD,
|
|
1736
|
+
RelationshipType.INHERITS,
|
|
1737
|
+
RelationshipType.OVERRIDES,
|
|
1738
|
+
RelationshipType.DEPENDS_ON_EXTERNAL,
|
|
1739
|
+
RelationshipType.IMPORTS,
|
|
1540
1740
|
]:
|
|
1541
1741
|
try:
|
|
1542
1742
|
result = await self._execute_query(
|
|
@@ -1549,7 +1749,12 @@ class CodebaseGraphManager:
|
|
|
1549
1749
|
|
|
1550
1750
|
# CALLS relationships (multiple tables)
|
|
1551
1751
|
calls_count = 0
|
|
1552
|
-
for table in [
|
|
1752
|
+
for table in [
|
|
1753
|
+
RelationshipType.CALLS,
|
|
1754
|
+
RelationshipType.CALLS_FM,
|
|
1755
|
+
RelationshipType.CALLS_MF,
|
|
1756
|
+
RelationshipType.CALLS_MM,
|
|
1757
|
+
]:
|
|
1553
1758
|
try:
|
|
1554
1759
|
result = await self._execute_query(
|
|
1555
1760
|
graph_id, f"MATCH ()-[r:{table}]->() RETURN COUNT(r) as count"
|
|
@@ -1563,16 +1768,21 @@ class CodebaseGraphManager:
|
|
|
1563
1768
|
|
|
1564
1769
|
# TRACKS relationships
|
|
1565
1770
|
tracks_count = 0
|
|
1566
|
-
for
|
|
1771
|
+
for tracks_rel in [
|
|
1772
|
+
RelationshipType.TRACKS_MODULE,
|
|
1773
|
+
RelationshipType.TRACKS_CLASS,
|
|
1774
|
+
RelationshipType.TRACKS_FUNCTION,
|
|
1775
|
+
RelationshipType.TRACKS_METHOD,
|
|
1776
|
+
]:
|
|
1567
1777
|
try:
|
|
1568
1778
|
result = await self._execute_query(
|
|
1569
1779
|
graph_id,
|
|
1570
|
-
f"MATCH ()-[r:
|
|
1780
|
+
f"MATCH ()-[r:{tracks_rel}]->() RETURN COUNT(r) as count",
|
|
1571
1781
|
)
|
|
1572
1782
|
if result:
|
|
1573
1783
|
tracks_count += result[0]["count"]
|
|
1574
1784
|
except Exception as e:
|
|
1575
|
-
logger.debug(f"Failed to count
|
|
1785
|
+
logger.debug(f"Failed to count {tracks_rel} relationships: {e}")
|
|
1576
1786
|
if tracks_count > 0:
|
|
1577
1787
|
rel_counts["TRACKS (total)"] = tracks_count
|
|
1578
1788
|
|
|
@@ -1586,16 +1796,16 @@ class CodebaseGraphManager:
|
|
|
1586
1796
|
|
|
1587
1797
|
# Print node stats
|
|
1588
1798
|
for node_type in [
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1799
|
+
NodeLabel.PROJECT,
|
|
1800
|
+
NodeLabel.PACKAGE,
|
|
1801
|
+
NodeLabel.MODULE,
|
|
1802
|
+
NodeLabel.CLASS,
|
|
1803
|
+
NodeLabel.FUNCTION,
|
|
1804
|
+
NodeLabel.METHOD,
|
|
1805
|
+
NodeLabel.FILE,
|
|
1806
|
+
NodeLabel.FOLDER,
|
|
1807
|
+
NodeLabel.FILE_METADATA,
|
|
1808
|
+
NodeLabel.DELETION_LOG,
|
|
1599
1809
|
]:
|
|
1600
1810
|
count = node_stats.get(node_type, 0)
|
|
1601
1811
|
logger.info(f"{node_type}: {count}")
|
|
@@ -1781,7 +1991,7 @@ class CodebaseGraphManager:
|
|
|
1781
1991
|
Graph ID of the graph being built
|
|
1782
1992
|
"""
|
|
1783
1993
|
repo_path = str(Path(repo_path).resolve())
|
|
1784
|
-
graph_id = self.
|
|
1994
|
+
graph_id = self.generate_graph_id(repo_path)
|
|
1785
1995
|
|
|
1786
1996
|
# Use repository name as default name
|
|
1787
1997
|
if not name:
|