claude-mpm 4.5.1__py3-none-any.whl → 4.5.5__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +33 -0
- claude_mpm/cli/__init__.py +27 -11
- claude_mpm/cli/commands/doctor.py +1 -4
- claude_mpm/core/config.py +2 -2
- claude_mpm/core/framework/__init__.py +6 -6
- claude_mpm/core/unified_paths.py +13 -12
- claude_mpm/hooks/kuzu_memory_hook.py +1 -1
- claude_mpm/init.py +19 -0
- claude_mpm/services/async_session_logger.py +6 -2
- claude_mpm/services/claude_session_logger.py +2 -2
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +2 -2
- claude_mpm/services/diagnostics/doctor_reporter.py +0 -2
- claude_mpm/services/mcp_config_manager.py +156 -105
- claude_mpm/services/mcp_gateway/core/process_pool.py +258 -36
- claude_mpm/services/mcp_gateway/utils/__init__.py +14 -0
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +160 -0
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +170 -0
- claude_mpm/services/mcp_service_verifier.py +4 -4
- claude_mpm/services/monitor/event_emitter.py +6 -2
- claude_mpm/services/project/archive_manager.py +7 -9
- claude_mpm/services/project/documentation_manager.py +2 -3
- claude_mpm/services/project/enhanced_analyzer.py +1 -1
- claude_mpm/services/project/project_organizer.py +15 -12
- claude_mpm/services/unified/__init__.py +13 -13
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +4 -8
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +0 -1
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +8 -9
- claude_mpm/services/unified/config_strategies/__init__.py +37 -37
- claude_mpm/services/unified/config_strategies/config_schema.py +18 -22
- claude_mpm/services/unified/config_strategies/context_strategy.py +6 -7
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +6 -10
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +5 -9
- claude_mpm/services/unified/config_strategies/unified_config_service.py +2 -4
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1 -1
- claude_mpm/services/unified/deployment_strategies/__init__.py +8 -8
- claude_mpm/services/unified/deployment_strategies/local.py +2 -5
- claude_mpm/services/unified/deployment_strategies/utils.py +13 -17
- claude_mpm/services/unified/deployment_strategies/vercel.py +5 -6
- claude_mpm/services/unified/unified_analyzer.py +1 -1
- claude_mpm/utils/common.py +3 -7
- claude_mpm/utils/database_connector.py +9 -12
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/METADATA +2 -2
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/RECORD +48 -45
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/WHEEL +0 -0
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/top_level.txt +0 -0
@@ -47,7 +47,9 @@ class MCPConfigManager:
|
|
47
47
|
STATIC_MCP_CONFIGS = {
|
48
48
|
"kuzu-memory": {
|
49
49
|
"type": "stdio",
|
50
|
-
|
50
|
+
# Use full path to kuzu-memory binary from pipx venv
|
51
|
+
# This ensures it runs with the correct Python version
|
52
|
+
"command": "kuzu-memory", # Will be resolved to pipx venv path
|
51
53
|
"args": ["mcp", "serve"], # v1.1.0+ uses 'mcp serve' command
|
52
54
|
},
|
53
55
|
"mcp-ticketer": {
|
@@ -580,57 +582,38 @@ class MCPConfigManager:
|
|
580
582
|
config["args"] = ["mcp"]
|
581
583
|
|
582
584
|
elif service_name == "kuzu-memory":
|
583
|
-
#
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
if test_cmd:
|
595
|
-
try:
|
596
|
-
result = subprocess.run(
|
597
|
-
test_cmd,
|
598
|
-
capture_output=True,
|
599
|
-
text=True,
|
600
|
-
timeout=10,
|
601
|
-
check=False,
|
602
|
-
)
|
603
|
-
# Check for MCP support in help output
|
604
|
-
help_output = result.stdout.lower() + result.stderr.lower()
|
605
|
-
|
606
|
-
# Standard version detection - look for "mcp serve" command (v1.1.0+)
|
607
|
-
# This is the correct format for kuzu-memory v1.1.0 and later
|
608
|
-
if "mcp serve" in help_output or (
|
609
|
-
"mcp" in help_output and "serve" in help_output
|
610
|
-
):
|
611
|
-
# Standard v1.1.0+ version with mcp serve command
|
612
|
-
kuzu_args = ["mcp", "serve"]
|
613
|
-
# Legacy version detection - only "serve" without "mcp"
|
614
|
-
elif "serve" in help_output and "mcp" not in help_output:
|
615
|
-
# Very old version that only has serve command
|
616
|
-
kuzu_args = ["serve"]
|
617
|
-
else:
|
618
|
-
# Default to the standard mcp serve format (v1.1.0+)
|
619
|
-
# Note: "claude mcp-server" format is deprecated and does not work
|
620
|
-
kuzu_args = ["mcp", "serve"]
|
621
|
-
except Exception:
|
622
|
-
# Default to the standard mcp serve command on any error
|
623
|
-
kuzu_args = ["mcp", "serve"]
|
585
|
+
# For kuzu-memory, prefer using the binary from pipx venv
|
586
|
+
# This ensures it runs with Python 3.12 instead of system Python 3.13
|
587
|
+
pipx_binary = (
|
588
|
+
Path.home()
|
589
|
+
/ ".local"
|
590
|
+
/ "pipx"
|
591
|
+
/ "venvs"
|
592
|
+
/ "kuzu-memory"
|
593
|
+
/ "bin"
|
594
|
+
/ "kuzu-memory"
|
595
|
+
)
|
624
596
|
|
625
|
-
if
|
597
|
+
if pipx_binary.exists():
|
598
|
+
# Use pipx venv binary directly - this runs with the correct Python
|
599
|
+
config["command"] = str(pipx_binary)
|
600
|
+
config["args"] = ["mcp", "serve"]
|
601
|
+
elif use_pipx_run:
|
602
|
+
# Fallback to pipx run
|
626
603
|
config["command"] = "pipx"
|
627
|
-
config["args"] = ["run", "kuzu-memory"
|
604
|
+
config["args"] = ["run", "kuzu-memory", "mcp", "serve"]
|
628
605
|
elif use_uvx:
|
606
|
+
# UVX fallback
|
629
607
|
config["command"] = "uvx"
|
630
|
-
config["args"] = ["kuzu-memory"
|
631
|
-
|
608
|
+
config["args"] = ["kuzu-memory", "mcp", "serve"]
|
609
|
+
elif service_path:
|
610
|
+
# Direct binary path
|
632
611
|
config["command"] = service_path
|
633
|
-
config["args"] =
|
612
|
+
config["args"] = ["mcp", "serve"]
|
613
|
+
else:
|
614
|
+
# Default fallback
|
615
|
+
config["command"] = "pipx"
|
616
|
+
config["args"] = ["run", "kuzu-memory", "mcp", "serve"]
|
634
617
|
|
635
618
|
# Generic config for unknown services
|
636
619
|
elif use_pipx_run:
|
@@ -741,9 +724,7 @@ class MCPConfigManager:
|
|
741
724
|
"command"
|
742
725
|
) or existing_config.get("args") != correct_config.get("args"):
|
743
726
|
needs_update = True
|
744
|
-
fixed_services.append(
|
745
|
-
f"{service_name} in {Path(project_key).name}"
|
746
|
-
)
|
727
|
+
fixed_services.append(f"{service_name} in {Path(project_key).name}")
|
747
728
|
|
748
729
|
# Update configuration if needed
|
749
730
|
if needs_update:
|
@@ -1017,49 +998,44 @@ class MCPConfigManager:
|
|
1017
998
|
|
1018
999
|
return False, "none"
|
1019
1000
|
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
return False
|
1059
|
-
|
1060
|
-
except Exception as e:
|
1061
|
-
self.logger.debug(f"Could not check/fix mcp-ticketer dependencies: {e}")
|
1062
|
-
return False
|
1001
|
+
# COMMENTED OUT: These functions are no longer used
|
1002
|
+
# Package maintainers should fix dependency declarations in their packages
|
1003
|
+
# Automatic dependency injection can cause conflicts and is not recommended
|
1004
|
+
|
1005
|
+
# def _get_mcp_ticketer_version(self) -> Optional[str]:
|
1006
|
+
# """Get the installed version of mcp-ticketer.
|
1007
|
+
#
|
1008
|
+
# Returns:
|
1009
|
+
# Version string (e.g., "0.1.8") or None if not installed
|
1010
|
+
# """
|
1011
|
+
# try:
|
1012
|
+
# result = subprocess.run(
|
1013
|
+
# ["pipx", "runpip", "mcp-ticketer", "show", "mcp-ticketer"],
|
1014
|
+
# capture_output=True,
|
1015
|
+
# text=True,
|
1016
|
+
# timeout=5,
|
1017
|
+
# check=False,
|
1018
|
+
# )
|
1019
|
+
#
|
1020
|
+
# if result.returncode == 0:
|
1021
|
+
# # Parse version from output
|
1022
|
+
# for line in result.stdout.split("\n"):
|
1023
|
+
# if line.startswith("Version:"):
|
1024
|
+
# return line.split(":", 1)[1].strip()
|
1025
|
+
# return None
|
1026
|
+
# except Exception:
|
1027
|
+
# return None
|
1028
|
+
#
|
1029
|
+
# def _check_and_fix_mcp_ticketer_dependencies(self) -> bool:
|
1030
|
+
# """Check and fix mcp-ticketer missing gql dependency.
|
1031
|
+
#
|
1032
|
+
# DEPRECATED: This workaround is no longer used.
|
1033
|
+
# Package maintainers should fix dependency declarations.
|
1034
|
+
#
|
1035
|
+
# Returns:
|
1036
|
+
# False (no longer performs injection)
|
1037
|
+
# """
|
1038
|
+
# return False
|
1063
1039
|
|
1064
1040
|
def fix_mcp_service_issues(self) -> Tuple[bool, str]:
|
1065
1041
|
"""
|
@@ -1109,24 +1085,26 @@ class MCPConfigManager:
|
|
1109
1085
|
)
|
1110
1086
|
success = self._reinstall_service(service_name)
|
1111
1087
|
if success:
|
1112
|
-
#
|
1113
|
-
|
1114
|
-
self._check_and_fix_mcp_ticketer_dependencies()
|
1088
|
+
# NOTE: Removed automatic dependency injection workaround
|
1089
|
+
# Package maintainers should fix dependency declarations
|
1115
1090
|
fixed_services.append(f"{service_name} (reinstalled)")
|
1116
1091
|
else:
|
1117
1092
|
failed_services.append(f"{service_name} (reinstall failed)")
|
1118
1093
|
|
1119
1094
|
elif issue_type == "missing_dependency":
|
1120
|
-
# Fix missing dependencies
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
)
|
1095
|
+
# Fix missing dependencies by automatically reinstalling
|
1096
|
+
self.logger.info(
|
1097
|
+
f" {service_name} has missing dependencies - auto-reinstalling..."
|
1098
|
+
)
|
1099
|
+
success = self._auto_reinstall_mcp_service(service_name)
|
1100
|
+
if success:
|
1101
|
+
fixed_services.append(f"{service_name} (auto-reinstalled)")
|
1128
1102
|
else:
|
1129
|
-
|
1103
|
+
self.logger.warning(
|
1104
|
+
f" Auto-reinstall failed for {service_name}. Manual fix: "
|
1105
|
+
f"pipx uninstall {service_name} && pipx install {service_name}"
|
1106
|
+
)
|
1107
|
+
failed_services.append(f"{service_name} (auto-reinstall failed)")
|
1130
1108
|
|
1131
1109
|
elif issue_type == "path_issue":
|
1132
1110
|
# Path issues are handled by config updates
|
@@ -1280,6 +1258,79 @@ class MCPConfigManager:
|
|
1280
1258
|
self.logger.error(f"Error reinstalling {service_name}: {e}")
|
1281
1259
|
return False
|
1282
1260
|
|
1261
|
+
def _auto_reinstall_mcp_service(self, service_name: str) -> bool:
|
1262
|
+
"""
|
1263
|
+
Automatically reinstall an MCP service with missing dependencies.
|
1264
|
+
|
1265
|
+
This method:
|
1266
|
+
1. Uninstalls the corrupted/incomplete service
|
1267
|
+
2. Reinstalls it fresh from pipx
|
1268
|
+
3. Verifies the reinstall was successful
|
1269
|
+
4. Updates status after successful reinstall
|
1270
|
+
|
1271
|
+
Args:
|
1272
|
+
service_name: Name of the MCP service to reinstall
|
1273
|
+
|
1274
|
+
Returns:
|
1275
|
+
True if reinstall successful, False otherwise
|
1276
|
+
"""
|
1277
|
+
try:
|
1278
|
+
import shutil
|
1279
|
+
|
1280
|
+
# Verify pipx is available
|
1281
|
+
if not shutil.which("pipx"):
|
1282
|
+
self.logger.error("pipx not found - cannot auto-reinstall")
|
1283
|
+
return False
|
1284
|
+
|
1285
|
+
self.logger.info(f" → Uninstalling {service_name}...")
|
1286
|
+
uninstall_result = subprocess.run(
|
1287
|
+
["pipx", "uninstall", service_name],
|
1288
|
+
capture_output=True,
|
1289
|
+
text=True,
|
1290
|
+
timeout=30,
|
1291
|
+
check=False,
|
1292
|
+
)
|
1293
|
+
|
1294
|
+
# Log result but don't fail if uninstall had issues
|
1295
|
+
if uninstall_result.returncode != 0:
|
1296
|
+
self.logger.debug(
|
1297
|
+
f"Uninstall had warnings (expected if corrupted): {uninstall_result.stderr}"
|
1298
|
+
)
|
1299
|
+
|
1300
|
+
self.logger.info(f" → Installing fresh {service_name}...")
|
1301
|
+
install_result = subprocess.run(
|
1302
|
+
["pipx", "install", service_name],
|
1303
|
+
capture_output=True,
|
1304
|
+
text=True,
|
1305
|
+
timeout=120,
|
1306
|
+
check=False,
|
1307
|
+
)
|
1308
|
+
|
1309
|
+
if install_result.returncode != 0:
|
1310
|
+
self.logger.error(
|
1311
|
+
f"Install failed for {service_name}: {install_result.stderr}"
|
1312
|
+
)
|
1313
|
+
return False
|
1314
|
+
|
1315
|
+
# Verify the reinstall worked
|
1316
|
+
self.logger.info(f" → Verifying {service_name} installation...")
|
1317
|
+
issue = self._detect_service_issue(service_name)
|
1318
|
+
|
1319
|
+
if issue is None:
|
1320
|
+
self.logger.info(f" ✅ Successfully reinstalled {service_name}")
|
1321
|
+
return True
|
1322
|
+
self.logger.warning(
|
1323
|
+
f"Reinstalled {service_name} but still has issue: {issue}"
|
1324
|
+
)
|
1325
|
+
return False
|
1326
|
+
|
1327
|
+
except subprocess.TimeoutExpired:
|
1328
|
+
self.logger.error(f"Timeout while reinstalling {service_name}")
|
1329
|
+
return False
|
1330
|
+
except Exception as e:
|
1331
|
+
self.logger.error(f"Error auto-reinstalling {service_name}: {e}")
|
1332
|
+
return False
|
1333
|
+
|
1283
1334
|
def _verify_service_installed(self, service_name: str, method: str) -> bool:
|
1284
1335
|
"""
|
1285
1336
|
Verify that a service was successfully installed and is functional.
|
@@ -20,6 +20,7 @@ import json
|
|
20
20
|
import os
|
21
21
|
import signal
|
22
22
|
import subprocess
|
23
|
+
import sys
|
23
24
|
import threading
|
24
25
|
import time
|
25
26
|
from pathlib import Path
|
@@ -484,6 +485,21 @@ async def auto_initialize_vector_search():
|
|
484
485
|
|
485
486
|
if vector_config.exists():
|
486
487
|
logger.debug(f"Vector search already initialized for {current_dir}")
|
488
|
+
|
489
|
+
# Ensure .mcp-vector-search is in gitignore even if already initialized
|
490
|
+
try:
|
491
|
+
from ....services.project.project_organizer import ProjectOrganizer
|
492
|
+
|
493
|
+
if (current_dir / ".claude-mpm").exists() or (
|
494
|
+
current_dir / ".git"
|
495
|
+
).exists():
|
496
|
+
organizer = ProjectOrganizer(current_dir)
|
497
|
+
organizer.update_gitignore(
|
498
|
+
additional_patterns=[".mcp-vector-search/"]
|
499
|
+
)
|
500
|
+
logger.debug("Ensured .mcp-vector-search is in gitignore")
|
501
|
+
except Exception as e:
|
502
|
+
logger.debug(f"Could not update gitignore for .mcp-vector-search: {e}")
|
487
503
|
# Check if index needs rebuilding (corrupted database)
|
488
504
|
chroma_db = current_dir / ".mcp-vector-search/chroma.sqlite3"
|
489
505
|
if chroma_db.exists():
|
@@ -518,6 +534,23 @@ async def auto_initialize_vector_search():
|
|
518
534
|
if proc.returncode == 0:
|
519
535
|
logger.info("✅ Vector search initialization completed")
|
520
536
|
|
537
|
+
# Ensure .mcp-vector-search is in gitignore
|
538
|
+
try:
|
539
|
+
from ....services.project.project_organizer import ProjectOrganizer
|
540
|
+
|
541
|
+
# Check if we're in a git repository (parent of .claude-mpm)
|
542
|
+
if (current_dir / ".claude-mpm").exists() or (
|
543
|
+
current_dir / ".git"
|
544
|
+
).exists():
|
545
|
+
organizer = ProjectOrganizer(current_dir)
|
546
|
+
organizer.update_gitignore(
|
547
|
+
additional_patterns=[".mcp-vector-search/"]
|
548
|
+
)
|
549
|
+
logger.debug("Ensured .mcp-vector-search is in gitignore")
|
550
|
+
except Exception as e:
|
551
|
+
logger.debug(f"Could not update gitignore for .mcp-vector-search: {e}")
|
552
|
+
# Non-critical, don't fail initialization
|
553
|
+
|
521
554
|
# Start background indexing (non-blocking)
|
522
555
|
def background_index():
|
523
556
|
try:
|
@@ -659,6 +692,12 @@ async def auto_initialize_kuzu_memory():
|
|
659
692
|
logger.debug("kuzu-memory command not found after installation")
|
660
693
|
return
|
661
694
|
|
695
|
+
# Check for kuzu-memory updates (non-blocking)
|
696
|
+
try:
|
697
|
+
await _check_kuzu_memory_updates(kuzu_memory_cmd)
|
698
|
+
except Exception as e:
|
699
|
+
logger.debug(f"Update check failed (non-critical): {e}")
|
700
|
+
|
662
701
|
# Initialize kuzu-memory database in current project
|
663
702
|
current_dir = Path.cwd()
|
664
703
|
kuzu_memories_dir = current_dir / "kuzu-memories"
|
@@ -668,6 +707,19 @@ async def auto_initialize_kuzu_memory():
|
|
668
707
|
logger.debug(
|
669
708
|
f"Kuzu-memory database already initialized at {kuzu_memories_dir}"
|
670
709
|
)
|
710
|
+
|
711
|
+
# Ensure kuzu-memories is in gitignore even if already initialized
|
712
|
+
try:
|
713
|
+
from ....services.project.project_organizer import ProjectOrganizer
|
714
|
+
|
715
|
+
if (current_dir / ".claude-mpm").exists() or (
|
716
|
+
current_dir / ".git"
|
717
|
+
).exists():
|
718
|
+
organizer = ProjectOrganizer(current_dir)
|
719
|
+
organizer.update_gitignore(additional_patterns=["kuzu-memories/"])
|
720
|
+
logger.debug("Ensured kuzu-memories is in gitignore")
|
721
|
+
except Exception as e:
|
722
|
+
logger.debug(f"Could not update gitignore for kuzu-memories: {e}")
|
671
723
|
else:
|
672
724
|
logger.info(
|
673
725
|
f"🎯 Initializing kuzu-memory database for project: {current_dir}"
|
@@ -687,6 +739,22 @@ async def auto_initialize_kuzu_memory():
|
|
687
739
|
|
688
740
|
if proc.returncode == 0:
|
689
741
|
logger.info("✅ Kuzu-memory database initialized successfully")
|
742
|
+
|
743
|
+
# Ensure kuzu-memories is in gitignore
|
744
|
+
try:
|
745
|
+
from ....services.project.project_organizer import ProjectOrganizer
|
746
|
+
|
747
|
+
if (current_dir / ".claude-mpm").exists() or (
|
748
|
+
current_dir / ".git"
|
749
|
+
).exists():
|
750
|
+
organizer = ProjectOrganizer(current_dir)
|
751
|
+
organizer.update_gitignore(
|
752
|
+
additional_patterns=["kuzu-memories/"]
|
753
|
+
)
|
754
|
+
logger.debug("Ensured kuzu-memories is in gitignore")
|
755
|
+
except Exception as e:
|
756
|
+
logger.debug(f"Could not update gitignore for kuzu-memories: {e}")
|
757
|
+
# Non-critical, don't fail initialization
|
690
758
|
else:
|
691
759
|
logger.warning(f"⚠️ Kuzu-memory initialization failed: {proc.stderr}")
|
692
760
|
|
@@ -694,47 +762,201 @@ async def auto_initialize_kuzu_memory():
|
|
694
762
|
logger.debug(f"Kuzu-memory auto-initialization error (non-critical): {e}")
|
695
763
|
|
696
764
|
|
697
|
-
async def
|
698
|
-
"""
|
699
|
-
|
700
|
-
await auto_initialize_vector_search()
|
765
|
+
async def _check_kuzu_memory_updates(kuzu_cmd: Path) -> None:
|
766
|
+
"""
|
767
|
+
Check for kuzu-memory updates and prompt user.
|
701
768
|
|
702
|
-
|
703
|
-
|
769
|
+
Args:
|
770
|
+
kuzu_cmd: Path to kuzu-memory command
|
704
771
|
|
705
|
-
|
772
|
+
WHY: Keep users informed about important updates that may fix bugs
|
773
|
+
or add features they need.
|
706
774
|
|
707
|
-
|
708
|
-
|
775
|
+
DESIGN DECISIONS:
|
776
|
+
- Non-blocking with timeout to prevent startup delays
|
777
|
+
- Respects user preferences and environment variables
|
778
|
+
- Only prompts in interactive TTY sessions
|
779
|
+
"""
|
780
|
+
logger = get_logger("kuzu_memory_update")
|
709
781
|
|
710
|
-
#
|
711
|
-
|
712
|
-
|
713
|
-
# Try project-local config
|
714
|
-
claude_config_path = Path.cwd() / ".claude.json"
|
782
|
+
# Skip if environment variable set
|
783
|
+
if os.environ.get("CLAUDE_MPM_SKIP_UPDATE_CHECK"):
|
784
|
+
return
|
715
785
|
|
716
|
-
if
|
717
|
-
|
718
|
-
|
719
|
-
config_data = json.load(f)
|
720
|
-
mcp_servers = config_data.get("mcpServers", {})
|
721
|
-
configs.update(mcp_servers)
|
722
|
-
except Exception as e:
|
723
|
-
get_logger("MCPProcessPool").warning(f"Failed to load Claude config: {e}")
|
786
|
+
# Skip if not TTY (can't prompt)
|
787
|
+
if not sys.stdin.isatty():
|
788
|
+
return
|
724
789
|
|
725
|
-
#
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
configs.update(mcp_servers)
|
733
|
-
except Exception as e:
|
734
|
-
get_logger("MCPProcessPool").warning(f"Failed to load MCP config: {e}")
|
790
|
+
# Import update utilities
|
791
|
+
from ..utils.package_version_checker import PackageVersionChecker
|
792
|
+
from ..utils.update_preferences import UpdatePreferences
|
793
|
+
|
794
|
+
# Check if updates are enabled for this package
|
795
|
+
if not UpdatePreferences.should_check_package("kuzu-memory"):
|
796
|
+
return
|
735
797
|
|
736
|
-
|
737
|
-
|
738
|
-
|
798
|
+
try:
|
799
|
+
# Get current version from pipx
|
800
|
+
result = subprocess.run(
|
801
|
+
["pipx", "list", "--json"],
|
802
|
+
capture_output=True,
|
803
|
+
text=True,
|
804
|
+
timeout=5,
|
805
|
+
check=False,
|
806
|
+
)
|
807
|
+
|
808
|
+
if result.returncode == 0:
|
809
|
+
pipx_data = json.loads(result.stdout)
|
810
|
+
venvs = pipx_data.get("venvs", {})
|
811
|
+
kuzu_info = venvs.get("kuzu-memory", {})
|
812
|
+
metadata = kuzu_info.get("metadata", {})
|
813
|
+
current_version = metadata.get("main_package", {}).get(
|
814
|
+
"package_version", "unknown"
|
815
|
+
)
|
816
|
+
|
817
|
+
if current_version != "unknown":
|
818
|
+
# Check for updates
|
819
|
+
checker = PackageVersionChecker()
|
820
|
+
update_info = await checker.check_for_update(
|
821
|
+
"kuzu-memory", current_version
|
822
|
+
)
|
739
823
|
|
740
|
-
|
824
|
+
if update_info and update_info.get("update_available"):
|
825
|
+
latest_version = update_info["latest"]
|
826
|
+
|
827
|
+
# Check if user wants to skip this version
|
828
|
+
if UpdatePreferences.should_skip_version(
|
829
|
+
"kuzu-memory", latest_version
|
830
|
+
):
|
831
|
+
logger.debug(
|
832
|
+
f"Skipping kuzu-memory update to {latest_version} per user preference"
|
833
|
+
)
|
834
|
+
return
|
835
|
+
|
836
|
+
# Prompt for update
|
837
|
+
_prompt_kuzu_update(update_info["current"], latest_version)
|
838
|
+
|
839
|
+
except Exception as e:
|
840
|
+
logger.debug(f"Update check error: {e}")
|
841
|
+
|
842
|
+
|
843
|
+
def _prompt_kuzu_update(current: str, latest: str) -> None:
|
844
|
+
"""
|
845
|
+
Prompt user to update kuzu-memory.
|
846
|
+
|
847
|
+
Args:
|
848
|
+
current: Current installed version
|
849
|
+
latest: Latest available version
|
850
|
+
"""
|
851
|
+
from ...cli.shared.error_handling import confirm_operation
|
852
|
+
from ..utils.update_preferences import UpdatePreferences
|
853
|
+
|
854
|
+
logger = get_logger("kuzu_memory_update")
|
855
|
+
|
856
|
+
message = (
|
857
|
+
f"\n🔄 A new version of kuzu-memory is available!\n"
|
858
|
+
f" Current: v{current}\n"
|
859
|
+
f" Latest: v{latest}\n\n"
|
860
|
+
f" This update may include bug fixes and performance improvements.\n"
|
861
|
+
f" Update now?"
|
862
|
+
)
|
863
|
+
|
864
|
+
# Check if running in a non-interactive context
|
865
|
+
try:
|
866
|
+
if confirm_operation(message):
|
867
|
+
print("🚀 Updating kuzu-memory...")
|
868
|
+
try:
|
869
|
+
result = subprocess.run(
|
870
|
+
["pipx", "upgrade", "kuzu-memory"],
|
871
|
+
capture_output=True,
|
872
|
+
text=True,
|
873
|
+
timeout=30,
|
874
|
+
check=False,
|
875
|
+
)
|
876
|
+
if result.returncode == 0:
|
877
|
+
print("✅ Successfully updated kuzu-memory!")
|
878
|
+
logger.info(f"Updated kuzu-memory from {current} to {latest}")
|
879
|
+
else:
|
880
|
+
print(f"⚠️ Update failed: {result.stderr}")
|
881
|
+
logger.warning(f"kuzu-memory update failed: {result.stderr}")
|
882
|
+
except subprocess.TimeoutExpired:
|
883
|
+
print("⚠️ Update timed out. Please try again later.")
|
884
|
+
logger.warning("kuzu-memory update timed out")
|
885
|
+
except Exception as e:
|
886
|
+
print(f"⚠️ Update failed: {e}")
|
887
|
+
logger.warning(f"kuzu-memory update error: {e}")
|
888
|
+
else:
|
889
|
+
# User declined update
|
890
|
+
print("\n To skip this version permanently, run:")
|
891
|
+
print(f" claude-mpm config set-skip-version kuzu-memory {latest}")
|
892
|
+
print(" To disable update checks for kuzu-memory:")
|
893
|
+
print(" claude-mpm config disable-update-checks kuzu-memory")
|
894
|
+
|
895
|
+
# Ask if user wants to skip this version
|
896
|
+
if confirm_operation("\n Skip this version in future checks?"):
|
897
|
+
UpdatePreferences.set_skip_version("kuzu-memory", latest)
|
898
|
+
print(f" Version {latest} will be skipped in future checks.")
|
899
|
+
except (KeyboardInterrupt, EOFError):
|
900
|
+
# User interrupted or input not available
|
901
|
+
pass
|
902
|
+
|
903
|
+
|
904
|
+
async def pre_warm_mcp_servers():
|
905
|
+
"""
|
906
|
+
Pre-warm MCP servers from configuration.
|
907
|
+
|
908
|
+
DISABLED: This function is currently disabled to avoid conflicts with
|
909
|
+
Claude Code's native MCP server management. When enabled, this can
|
910
|
+
cause issues with MCP server initialization and stderr/stdout handling.
|
911
|
+
|
912
|
+
TODO: Re-enable after ensuring compatibility with Claude Code's MCP handling.
|
913
|
+
"""
|
914
|
+
logger = get_logger("MCPProcessPool")
|
915
|
+
logger.debug("MCP server pre-warming is currently disabled")
|
916
|
+
|
917
|
+
# COMMENTED OUT: Auto-initialization that can interfere with Claude Code
|
918
|
+
# # Auto-initialize vector search for current project
|
919
|
+
# await auto_initialize_vector_search()
|
920
|
+
#
|
921
|
+
# # Auto-initialize kuzu-memory for persistent knowledge
|
922
|
+
# await auto_initialize_kuzu_memory()
|
923
|
+
#
|
924
|
+
# pool = get_process_pool()
|
925
|
+
#
|
926
|
+
# # Load MCP configurations
|
927
|
+
# configs = {}
|
928
|
+
#
|
929
|
+
# # Check .claude.json for MCP server configs
|
930
|
+
# claude_config_path = Path.home() / ".claude.json"
|
931
|
+
# if not claude_config_path.exists():
|
932
|
+
# # Try project-local config
|
933
|
+
# claude_config_path = Path.cwd() / ".claude.json"
|
934
|
+
#
|
935
|
+
# if claude_config_path.exists():
|
936
|
+
# try:
|
937
|
+
# with open(claude_config_path) as f:
|
938
|
+
# config_data = json.load(f)
|
939
|
+
# mcp_servers = config_data.get("mcpServers", {})
|
940
|
+
# configs.update(mcp_servers)
|
941
|
+
# except Exception as e:
|
942
|
+
# get_logger("MCPProcessPool").warning(f"Failed to load Claude config: {e}")
|
943
|
+
#
|
944
|
+
# # Check .mcp.json for additional configs
|
945
|
+
# mcp_config_path = Path.cwd() / ".mcp.json"
|
946
|
+
# if mcp_config_path.exists():
|
947
|
+
# try:
|
948
|
+
# with open(mcp_config_path) as f:
|
949
|
+
# config_data = json.load(f)
|
950
|
+
# mcp_servers = config_data.get("mcpServers", {})
|
951
|
+
# configs.update(mcp_servers)
|
952
|
+
# except Exception as e:
|
953
|
+
# get_logger("MCPProcessPool").warning(f"Failed to load MCP config: {e}")
|
954
|
+
#
|
955
|
+
# if configs:
|
956
|
+
# await pool.pre_warm_servers(configs)
|
957
|
+
# await pool.start_health_monitoring()
|
958
|
+
#
|
959
|
+
# return pool
|
960
|
+
|
961
|
+
# Return a basic pool instance without pre-warming
|
962
|
+
return get_process_pool()
|