claude-mpm 4.5.1__py3-none-any.whl → 4.5.6__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 +29 -3
- claude_mpm/services/claude_session_logger.py +11 -3
- 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 -10
- claude_mpm/services/project/documentation_manager.py +2 -4
- claude_mpm/services/project/enhanced_analyzer.py +1 -1
- claude_mpm/services/project/project_organizer.py +12 -12
- claude_mpm/services/unified/__init__.py +13 -13
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +4 -9
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +0 -1
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +10 -7
- 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 -9
- 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.6.dist-info}/METADATA +2 -2
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.6.dist-info}/RECORD +48 -45
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.6.dist-info}/WHEEL +0 -0
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.6.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.6.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.6.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.5.
|
1
|
+
4.5.6
|
@@ -61,6 +61,10 @@
|
|
61
61
|
❌ "No issues found" without scan results → MUST have scan evidence
|
62
62
|
❌ "Performance improved" without metrics → MUST have measurement data
|
63
63
|
❌ "Security enhanced" without audit → MUST have security verification
|
64
|
+
❌ "Running on localhost:XXXX" without fetch verification → MUST have HTTP response evidence
|
65
|
+
❌ "Server started successfully" without log evidence → MUST have process/log verification
|
66
|
+
❌ "Application available at..." without accessibility test → MUST have endpoint check
|
67
|
+
❌ "You can now access..." without verification → MUST have browser/fetch test
|
64
68
|
|
65
69
|
## ONLY ALLOWED PM TOOLS
|
66
70
|
✓ Task - For delegation to agents (PRIMARY TOOL - USE THIS 90% OF TIME)
|
@@ -267,6 +271,30 @@ Requirements:
|
|
267
271
|
5. Report: "Deployment VERIFIED" or "Deployment FAILED: [specific issues]"
|
268
272
|
```
|
269
273
|
|
274
|
+
## LOCAL DEPLOYMENT MANDATORY VERIFICATION
|
275
|
+
|
276
|
+
**CRITICAL**: PM MUST NEVER claim "running on localhost" without verification.
|
277
|
+
|
278
|
+
### Required for ALL Local Deployments (PM2, Docker, npm start, etc.):
|
279
|
+
1. PM MUST delegate to local-ops-agent (or Ops) for deployment
|
280
|
+
2. Ops agent MUST verify with ALL of these:
|
281
|
+
- Process status check (ps, pm2 status, docker ps)
|
282
|
+
- Log examination for startup errors
|
283
|
+
- Fetch test to claimed URL (e.g., curl http://localhost:3000)
|
284
|
+
- Response validation (HTTP status code, content check)
|
285
|
+
3. PM can ONLY report success WITH evidence:
|
286
|
+
- ✅ "Verified running at localhost:3000: [HTTP 200 response]"
|
287
|
+
- ❌ "Should be running on localhost:3000" (VIOLATION)
|
288
|
+
- ❌ "Application is available at..." (VIOLATION without proof)
|
289
|
+
|
290
|
+
### Automatic Violation Triggers for Localhost Claims:
|
291
|
+
These phrases without fetch evidence = IMMEDIATE VIOLATION:
|
292
|
+
- "running on localhost"
|
293
|
+
- "available at localhost"
|
294
|
+
- "access at http://localhost"
|
295
|
+
- "server started on port"
|
296
|
+
- "deployment successful" (without verification)
|
297
|
+
|
270
298
|
## QA Requirements
|
271
299
|
|
272
300
|
**Rule**: No QA = Work incomplete
|
@@ -375,6 +403,11 @@ When PM attempts forbidden action:
|
|
375
403
|
- "I think" → VIOLATION: Need agent analysis
|
376
404
|
- "Probably" → VIOLATION: Need verification
|
377
405
|
|
406
|
+
**Localhost Assertion Red Flags:**
|
407
|
+
- "Running on localhost" → VIOLATION: Need fetch verification
|
408
|
+
- "Server is up" → VIOLATION: Need process + fetch proof
|
409
|
+
- "You can access" → VIOLATION: Need endpoint test
|
410
|
+
|
378
411
|
### ✅ CORRECT PM PHRASES:
|
379
412
|
- "I'll delegate this to..."
|
380
413
|
- "I'll have [Agent] handle..."
|
claude_mpm/cli/__init__.py
CHANGED
@@ -222,6 +222,14 @@ def _check_mcp_auto_configuration():
|
|
222
222
|
logger = get_logger("cli")
|
223
223
|
logger.debug(f"MCP auto-configuration check failed: {e}")
|
224
224
|
|
225
|
+
# Skip MCP service fixes for the doctor command
|
226
|
+
# The doctor command performs its own comprehensive MCP service check
|
227
|
+
# Running both would cause duplicate checks and log messages (9 seconds apart)
|
228
|
+
import sys
|
229
|
+
|
230
|
+
if len(sys.argv) > 1 and sys.argv[1] == "doctor":
|
231
|
+
return
|
232
|
+
|
225
233
|
# Also ensure MCP services are properly configured in ~/.claude.json
|
226
234
|
# This fixes incorrect paths and adds missing services
|
227
235
|
try:
|
@@ -289,23 +297,31 @@ def _verify_mcp_gateway_startup():
|
|
289
297
|
# Quick check first - if already configured, skip detailed verification
|
290
298
|
gateway_configured = is_mcp_gateway_configured()
|
291
299
|
|
292
|
-
# Pre-
|
293
|
-
# This
|
300
|
+
# DISABLED: Pre-warming MCP servers can interfere with Claude Code's MCP management
|
301
|
+
# This was causing issues with MCP server initialization and stderr handling
|
302
|
+
# def run_pre_warming():
|
303
|
+
# loop = None
|
304
|
+
# try:
|
305
|
+
# start_time = time.time()
|
306
|
+
# loop = asyncio.new_event_loop()
|
307
|
+
# asyncio.set_event_loop(loop)
|
308
|
+
#
|
309
|
+
# # Pre-warm MCP servers (especially vector search)
|
310
|
+
# logger.info("Pre-warming MCP servers to eliminate startup delay...")
|
311
|
+
# loop.run_until_complete(pre_warm_mcp_servers())
|
312
|
+
#
|
313
|
+
# pre_warm_time = time.time() - start_time
|
314
|
+
# if pre_warm_time > 1.0:
|
315
|
+
# logger.info(f"MCP servers pre-warmed in {pre_warm_time:.2f}s")
|
316
|
+
|
317
|
+
# Dummy function to maintain structure
|
294
318
|
def run_pre_warming():
|
295
319
|
loop = None
|
296
320
|
try:
|
297
|
-
|
321
|
+
time.time()
|
298
322
|
loop = asyncio.new_event_loop()
|
299
323
|
asyncio.set_event_loop(loop)
|
300
324
|
|
301
|
-
# Pre-warm MCP servers (especially vector search)
|
302
|
-
logger.info("Pre-warming MCP servers to eliminate startup delay...")
|
303
|
-
loop.run_until_complete(pre_warm_mcp_servers())
|
304
|
-
|
305
|
-
pre_warm_time = time.time() - start_time
|
306
|
-
if pre_warm_time > 1.0:
|
307
|
-
logger.info(f"MCP servers pre-warmed in {pre_warm_time:.2f}s")
|
308
|
-
|
309
325
|
# Also run gateway verification if needed
|
310
326
|
if not gateway_configured:
|
311
327
|
loop.run_until_complete(verify_mcp_gateway_on_startup())
|
@@ -137,10 +137,7 @@ def doctor_command(args):
|
|
137
137
|
output_format = "markdown"
|
138
138
|
elif output_file:
|
139
139
|
# Force markdown format when writing to file (unless json specified)
|
140
|
-
if str(output_file).endswith(".json")
|
141
|
-
output_format = "json"
|
142
|
-
else:
|
143
|
-
output_format = "markdown"
|
140
|
+
output_format = "json" if str(output_file).endswith(".json") else "markdown"
|
144
141
|
else:
|
145
142
|
output_format = "terminal"
|
146
143
|
|
claude_mpm/core/config.py
CHANGED
@@ -56,7 +56,7 @@ class Config:
|
|
56
56
|
# Double-check locking pattern for thread safety
|
57
57
|
if cls._instance is None:
|
58
58
|
cls._instance = super().__new__(cls)
|
59
|
-
logger.
|
59
|
+
logger.debug("Creating new Config singleton instance")
|
60
60
|
else:
|
61
61
|
logger.debug(
|
62
62
|
"Reusing existing Config singleton instance (concurrent init)"
|
@@ -102,7 +102,7 @@ class Config:
|
|
102
102
|
return
|
103
103
|
|
104
104
|
Config._initialized = True
|
105
|
-
logger.
|
105
|
+
logger.debug("Initializing Config singleton for the first time")
|
106
106
|
|
107
107
|
# Initialize instance variables inside the lock to ensure thread safety
|
108
108
|
self._config: Dict[str, Any] = {}
|
@@ -22,17 +22,17 @@ from .processors import (
|
|
22
22
|
)
|
23
23
|
|
24
24
|
__all__ = [
|
25
|
-
# Loaders
|
26
|
-
"FileLoader",
|
27
|
-
"PackagedLoader",
|
28
|
-
"InstructionLoader",
|
29
25
|
"AgentLoader",
|
26
|
+
"CapabilityGenerator",
|
30
27
|
# Formatters
|
31
28
|
"ContentFormatter",
|
32
|
-
"CapabilityGenerator",
|
33
29
|
"ContextGenerator",
|
30
|
+
# Loaders
|
31
|
+
"FileLoader",
|
32
|
+
"InstructionLoader",
|
33
|
+
"MemoryProcessor",
|
34
34
|
# Processors
|
35
35
|
"MetadataProcessor",
|
36
|
+
"PackagedLoader",
|
36
37
|
"TemplateProcessor",
|
37
|
-
"MemoryProcessor",
|
38
38
|
]
|
claude_mpm/core/unified_paths.py
CHANGED
@@ -178,7 +178,7 @@ class PathContext:
|
|
178
178
|
"""
|
179
179
|
# Check for environment variable override
|
180
180
|
if os.environ.get("CLAUDE_MPM_DEV_MODE", "").lower() in ("1", "true", "yes"):
|
181
|
-
logger.
|
181
|
+
logger.debug(
|
182
182
|
"Development mode forced via CLAUDE_MPM_DEV_MODE environment variable"
|
183
183
|
)
|
184
184
|
return DeploymentContext.DEVELOPMENT
|
@@ -198,10 +198,10 @@ class PathContext:
|
|
198
198
|
'name = "claude-mpm"' in pyproject_content
|
199
199
|
or '"claude-mpm"' in pyproject_content
|
200
200
|
):
|
201
|
-
logger.
|
201
|
+
logger.debug(
|
202
202
|
f"Detected claude-mpm development directory at {current}"
|
203
203
|
)
|
204
|
-
logger.
|
204
|
+
logger.debug(
|
205
205
|
"Using development mode for local source preference"
|
206
206
|
)
|
207
207
|
return DeploymentContext.DEVELOPMENT
|
@@ -219,7 +219,7 @@ class PathContext:
|
|
219
219
|
# First check if this is an editable install, regardless of path
|
220
220
|
# This is important for cases where pipx points to a development installation
|
221
221
|
if PathContext._is_editable_install():
|
222
|
-
logger.
|
222
|
+
logger.debug("Detected editable/development installation")
|
223
223
|
# Check if we should use development paths
|
224
224
|
# This could be because we're in a src/ directory or running from dev directory
|
225
225
|
if module_path.parent.name == "src":
|
@@ -233,7 +233,7 @@ class PathContext:
|
|
233
233
|
if (current / "src" / "claude_mpm").exists() and (
|
234
234
|
current / "pyproject.toml"
|
235
235
|
).exists():
|
236
|
-
logger.
|
236
|
+
logger.debug(
|
237
237
|
"Running pipx from development directory, using development mode"
|
238
238
|
)
|
239
239
|
return DeploymentContext.DEVELOPMENT
|
@@ -249,33 +249,33 @@ class PathContext:
|
|
249
249
|
module_path.parent.name == "src"
|
250
250
|
and (module_path.parent.parent / "src" / "claude_mpm").exists()
|
251
251
|
):
|
252
|
-
logger.
|
252
|
+
logger.debug(
|
253
253
|
f"Detected development mode via directory structure at {module_path}"
|
254
254
|
)
|
255
255
|
return DeploymentContext.DEVELOPMENT
|
256
256
|
|
257
257
|
# Check for pipx install
|
258
258
|
if "pipx" in str(module_path):
|
259
|
-
logger.
|
259
|
+
logger.debug(f"Detected pipx installation at {module_path}")
|
260
260
|
return DeploymentContext.PIPX_INSTALL
|
261
261
|
|
262
262
|
# Check for system package
|
263
263
|
if "dist-packages" in str(module_path):
|
264
|
-
logger.
|
264
|
+
logger.debug(f"Detected system package installation at {module_path}")
|
265
265
|
return DeploymentContext.SYSTEM_PACKAGE
|
266
266
|
|
267
267
|
# Check for site-packages (could be pip or editable)
|
268
268
|
if "site-packages" in str(module_path):
|
269
269
|
# Already checked for editable above, so this is a regular pip install
|
270
|
-
logger.
|
270
|
+
logger.debug(f"Detected pip installation at {module_path}")
|
271
271
|
return DeploymentContext.PIP_INSTALL
|
272
272
|
|
273
273
|
# Default to pip install
|
274
|
-
logger.
|
274
|
+
logger.debug(f"Defaulting to pip installation for {module_path}")
|
275
275
|
return DeploymentContext.PIP_INSTALL
|
276
276
|
|
277
277
|
except ImportError:
|
278
|
-
logger.
|
278
|
+
logger.debug(
|
279
279
|
"ImportError during context detection, defaulting to development"
|
280
280
|
)
|
281
281
|
return DeploymentContext.DEVELOPMENT
|
@@ -321,7 +321,8 @@ class UnifiedPathManager:
|
|
321
321
|
]
|
322
322
|
self._initialized = True
|
323
323
|
|
324
|
-
|
324
|
+
# Use debug level for initialization details
|
325
|
+
logger.debug(
|
325
326
|
f"UnifiedPathManager initialized with context: {self._deployment_context.value}"
|
326
327
|
)
|
327
328
|
|
@@ -230,7 +230,7 @@ Note: Use the memories above to provide more informed and contextual responses.
|
|
230
230
|
# Extract memory content and metadata
|
231
231
|
content = memory.get("content", "")
|
232
232
|
tags = memory.get("tags", [])
|
233
|
-
|
233
|
+
memory.get("timestamp", "")
|
234
234
|
relevance = memory.get("relevance", 0.0)
|
235
235
|
|
236
236
|
# Format memory entry
|
claude_mpm/init.py
CHANGED
@@ -141,6 +141,25 @@ class ProjectInitializer:
|
|
141
141
|
if not gitignore.exists():
|
142
142
|
gitignore.write_text("logs/\n*.log\n*.pyc\n__pycache__/\n")
|
143
143
|
|
144
|
+
# Also ensure MCP directories are in main project .gitignore
|
145
|
+
try:
|
146
|
+
from claude_mpm.services.project.project_organizer import (
|
147
|
+
ProjectOrganizer,
|
148
|
+
)
|
149
|
+
|
150
|
+
# Check if we're in a git repository
|
151
|
+
if (project_root / ".git").exists():
|
152
|
+
organizer = ProjectOrganizer(project_root)
|
153
|
+
# This will add MCP directories and other standard patterns
|
154
|
+
organizer.update_gitignore()
|
155
|
+
self.logger.debug(
|
156
|
+
"Updated project .gitignore with MCP and standard patterns"
|
157
|
+
)
|
158
|
+
except Exception as e:
|
159
|
+
self.logger.debug(
|
160
|
+
f"Could not update project gitignore with MCP patterns: {e}"
|
161
|
+
)
|
162
|
+
|
144
163
|
# Log successful creation with details
|
145
164
|
self.logger.info(f"Initialized project directory at {self.project_dir}")
|
146
165
|
self.logger.debug("Created directories: agents, config, responses, logs")
|
@@ -175,6 +175,11 @@ class AsyncSessionLogger:
|
|
175
175
|
if self.enable_async:
|
176
176
|
self._start_worker()
|
177
177
|
|
178
|
+
# Log initialization status
|
179
|
+
logger.info(
|
180
|
+
f"AsyncSessionLogger initialized: session_id={self.session_id}, async={self.enable_async}, format={self.log_format.value}"
|
181
|
+
)
|
182
|
+
|
178
183
|
def _get_claude_session_id(self) -> str:
|
179
184
|
"""Get or generate a Claude session ID."""
|
180
185
|
# Check environment variables in order of preference
|
@@ -471,8 +476,19 @@ class AsyncSessionLogger:
|
|
471
476
|
Args:
|
472
477
|
timeout: Maximum time to wait for shutdown
|
473
478
|
"""
|
479
|
+
# Only log shutdown if we're actually shutting down an active logger
|
480
|
+
if self._shutdown:
|
481
|
+
logger.debug("AsyncSessionLogger already shut down")
|
482
|
+
return
|
483
|
+
|
474
484
|
if self.enable_async:
|
475
|
-
|
485
|
+
# Only log at INFO level if we actually processed something
|
486
|
+
if self.stats.get("logged", 0) > 0 or self.stats.get("queued", 0) > 0:
|
487
|
+
logger.info(
|
488
|
+
f"Shutting down async logger (logged: {self.stats.get('logged', 0)}, queued: {self.stats.get('queued', 0)})"
|
489
|
+
)
|
490
|
+
else:
|
491
|
+
logger.debug("Shutting down async logger (no activity)")
|
476
492
|
|
477
493
|
# Signal shutdown
|
478
494
|
self._shutdown = True
|
@@ -484,8 +500,18 @@ class AsyncSessionLogger:
|
|
484
500
|
if self._worker_thread.is_alive():
|
485
501
|
logger.warning("Worker thread did not shutdown cleanly")
|
486
502
|
|
487
|
-
# Log final statistics
|
488
|
-
|
503
|
+
# Log final statistics only if we actually logged something
|
504
|
+
if self.stats.get("logged", 0) > 0:
|
505
|
+
logger.info(f"AsyncSessionLogger final stats: {self.stats}")
|
506
|
+
elif self.stats.get("queued", 0) > 0 or self.stats.get("dropped", 0) > 0:
|
507
|
+
logger.debug(
|
508
|
+
f"AsyncSessionLogger stats (incomplete session): {self.stats}"
|
509
|
+
)
|
510
|
+
else:
|
511
|
+
# Use debug level when nothing was logged
|
512
|
+
logger.debug(
|
513
|
+
f"AsyncSessionLogger stats (no sessions logged): {self.stats}"
|
514
|
+
)
|
489
515
|
|
490
516
|
def get_stats(self) -> Dict[str, Any]:
|
491
517
|
"""Get logger statistics."""
|
@@ -101,8 +101,16 @@ class ClaudeSessionLogger:
|
|
101
101
|
|
102
102
|
if self.use_async:
|
103
103
|
try:
|
104
|
+
# Pass our session_id to async logger to avoid duplicate generation
|
104
105
|
self._async_logger = get_async_logger(config=config)
|
105
|
-
|
106
|
+
# Synchronize session IDs - use the one we already generated
|
107
|
+
if self.session_id and hasattr(self._async_logger, "set_session_id"):
|
108
|
+
self._async_logger.set_session_id(self.session_id)
|
109
|
+
logger.info(
|
110
|
+
f"Using async logger with session ID: {self.session_id}"
|
111
|
+
)
|
112
|
+
else:
|
113
|
+
logger.info("Using async logger for improved performance")
|
106
114
|
except Exception as e:
|
107
115
|
logger.warning(
|
108
116
|
f"Failed to initialize async logger, falling back to sync: {e}"
|
@@ -135,9 +143,9 @@ class ClaudeSessionLogger:
|
|
135
143
|
if not session_id:
|
136
144
|
# Use a timestamp-based session ID as fallback
|
137
145
|
session_id = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
138
|
-
logger.info(f"
|
146
|
+
logger.info(f"Session ID: {session_id} (generated)")
|
139
147
|
else:
|
140
|
-
logger.info(f"
|
148
|
+
logger.info(f"Session ID: {session_id}")
|
141
149
|
|
142
150
|
return session_id
|
143
151
|
|
@@ -195,7 +195,7 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
195
195
|
|
196
196
|
# Determine overall status
|
197
197
|
errors = [r for r in sub_results if r.status == DiagnosticStatus.ERROR]
|
198
|
-
|
198
|
+
[r for r in sub_results if r.status == DiagnosticStatus.WARNING]
|
199
199
|
|
200
200
|
if errors:
|
201
201
|
status = DiagnosticStatus.ERROR
|
@@ -508,7 +508,7 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
508
508
|
# Special case for kuzu-memory with args
|
509
509
|
mcp_command = ["pipx", "run", base_cmd[0]] + base_cmd[1:]
|
510
510
|
else:
|
511
|
-
mcp_command = ["pipx", "run"
|
511
|
+
mcp_command = ["pipx", "run", *base_cmd]
|
512
512
|
else:
|
513
513
|
mcp_command = config["mcp_command"]
|
514
514
|
|