claude-mpm 4.1.2__py3-none-any.whl → 4.1.3__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/templates/engineer.json +33 -11
- claude_mpm/cli/commands/agents.py +556 -1009
- claude_mpm/cli/commands/memory.py +248 -927
- claude_mpm/cli/commands/run.py +139 -484
- claude_mpm/cli/startup_logging.py +76 -0
- claude_mpm/core/agent_registry.py +6 -10
- claude_mpm/core/framework_loader.py +114 -595
- claude_mpm/core/logging_config.py +2 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
- claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
- claude_mpm/services/agents/memory/memory_file_service.py +103 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
- claude_mpm/services/agents/registry/__init__.py +1 -1
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +407 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +589 -0
- claude_mpm/services/cli/dashboard_launcher.py +424 -0
- claude_mpm/services/cli/memory_crud_service.py +617 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/session_manager.py +513 -0
- claude_mpm/services/cli/socketio_manager.py +498 -0
- claude_mpm/services/cli/startup_checker.py +370 -0
- claude_mpm/services/core/cache_manager.py +311 -0
- claude_mpm/services/core/memory_manager.py +637 -0
- claude_mpm/services/core/path_resolver.py +498 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/RECORD +52 -22
- claude_mpm/cli/commands/run_config_checker.py +0 -159
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Path Resolution Service
|
|
3
|
+
=======================
|
|
4
|
+
|
|
5
|
+
This module provides the PathResolver service for handling all path resolution logic
|
|
6
|
+
that was previously embedded in FrameworkLoader. It manages:
|
|
7
|
+
- Framework path detection (packaged vs development)
|
|
8
|
+
- NPM global path resolution
|
|
9
|
+
- Deployment context management
|
|
10
|
+
- Instruction file path resolution with precedence
|
|
11
|
+
- Cross-platform path handling
|
|
12
|
+
|
|
13
|
+
The service consolidates path management logic while maintaining backward compatibility.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import subprocess
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Dict, Optional, Tuple
|
|
21
|
+
|
|
22
|
+
from .service_interfaces import ICacheManager, IPathResolver
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DeploymentContext(Enum):
|
|
26
|
+
"""Deployment context enumeration."""
|
|
27
|
+
|
|
28
|
+
DEVELOPMENT = "development"
|
|
29
|
+
EDITABLE_INSTALL = "editable_install"
|
|
30
|
+
PIP_INSTALL = "pip_install"
|
|
31
|
+
PIPX_INSTALL = "pipx_install"
|
|
32
|
+
SYSTEM_PACKAGE = "system_package"
|
|
33
|
+
UNKNOWN = "unknown"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PathResolver(IPathResolver):
|
|
37
|
+
"""
|
|
38
|
+
Service for resolving and managing paths in the claude-mpm framework.
|
|
39
|
+
|
|
40
|
+
This service extracts path resolution logic from FrameworkLoader to provide
|
|
41
|
+
a focused, reusable service for path management across the application.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, cache_manager: Optional[ICacheManager] = None):
|
|
45
|
+
"""
|
|
46
|
+
Initialize the PathResolver service.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
cache_manager: Optional cache manager for caching resolved paths
|
|
50
|
+
"""
|
|
51
|
+
self.logger = logging.getLogger(__name__)
|
|
52
|
+
self.cache_manager = cache_manager
|
|
53
|
+
self._framework_path: Optional[Path] = None
|
|
54
|
+
self._deployment_context: Optional[DeploymentContext] = None
|
|
55
|
+
self._path_cache: Dict[str, str] = {} # Internal cache for paths
|
|
56
|
+
|
|
57
|
+
def resolve_path(self, path: str, base_dir: Optional[Path] = None) -> Path:
|
|
58
|
+
"""
|
|
59
|
+
Resolve a path relative to a base directory.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
path: The path to resolve (can be relative or absolute)
|
|
63
|
+
base_dir: Base directory for relative paths (defaults to cwd)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The resolved absolute path
|
|
67
|
+
"""
|
|
68
|
+
path_obj = Path(path)
|
|
69
|
+
|
|
70
|
+
if path_obj.is_absolute():
|
|
71
|
+
return path_obj
|
|
72
|
+
|
|
73
|
+
if base_dir is None:
|
|
74
|
+
base_dir = Path.cwd()
|
|
75
|
+
|
|
76
|
+
return (base_dir / path_obj).resolve()
|
|
77
|
+
|
|
78
|
+
def validate_path(self, path: Path, must_exist: bool = False) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Validate a path for security and existence.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
path: The path to validate
|
|
84
|
+
must_exist: Whether the path must exist
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
True if path is valid, False otherwise
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
# Resolve to absolute path to check for path traversal
|
|
91
|
+
resolved = path.resolve()
|
|
92
|
+
|
|
93
|
+
# Check if path exists if required
|
|
94
|
+
if must_exist and not resolved.exists():
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
return True
|
|
98
|
+
except (OSError, ValueError):
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
def ensure_directory(self, path: Path) -> Path:
|
|
102
|
+
"""
|
|
103
|
+
Ensure a directory exists, creating it if necessary.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
path: The directory path
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
The directory path
|
|
110
|
+
"""
|
|
111
|
+
path = path.resolve()
|
|
112
|
+
if not path.exists():
|
|
113
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
self.logger.debug(f"Created directory: {path}")
|
|
115
|
+
elif not path.is_dir():
|
|
116
|
+
raise ValueError(f"Path exists but is not a directory: {path}")
|
|
117
|
+
return path
|
|
118
|
+
|
|
119
|
+
def find_project_root(self, start_path: Optional[Path] = None) -> Optional[Path]:
|
|
120
|
+
"""
|
|
121
|
+
Find the project root directory.
|
|
122
|
+
|
|
123
|
+
Looks for common project indicators like .git, pyproject.toml, package.json, etc.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
start_path: Starting path for search (defaults to cwd)
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Project root path or None if not found
|
|
130
|
+
"""
|
|
131
|
+
if start_path is None:
|
|
132
|
+
start_path = Path.cwd()
|
|
133
|
+
|
|
134
|
+
start_path = start_path.resolve()
|
|
135
|
+
|
|
136
|
+
# If start_path is a file, use its parent directory
|
|
137
|
+
if start_path.is_file():
|
|
138
|
+
start_path = start_path.parent
|
|
139
|
+
|
|
140
|
+
# Common project root indicators
|
|
141
|
+
root_indicators = [
|
|
142
|
+
".git",
|
|
143
|
+
"pyproject.toml",
|
|
144
|
+
"setup.py",
|
|
145
|
+
"setup.cfg",
|
|
146
|
+
"package.json",
|
|
147
|
+
"Cargo.toml",
|
|
148
|
+
"go.mod",
|
|
149
|
+
"pom.xml",
|
|
150
|
+
"build.gradle",
|
|
151
|
+
".claude-mpm", # Claude MPM specific
|
|
152
|
+
"CLAUDE.md", # Claude project instructions
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
current = start_path
|
|
156
|
+
while current != current.parent: # Stop at filesystem root
|
|
157
|
+
for indicator in root_indicators:
|
|
158
|
+
if (current / indicator).exists():
|
|
159
|
+
self.logger.debug(
|
|
160
|
+
f"Found project root at {current} (indicator: {indicator})"
|
|
161
|
+
)
|
|
162
|
+
return current
|
|
163
|
+
current = current.parent
|
|
164
|
+
|
|
165
|
+
# If no indicators found, return None
|
|
166
|
+
self.logger.debug(f"No project root found from {start_path}")
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
def detect_framework_path(self) -> Optional[Path]:
|
|
170
|
+
"""
|
|
171
|
+
Auto-detect claude-mpm framework using unified path management.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Path to framework root or Path("__PACKAGED__") for packaged installations,
|
|
175
|
+
None if framework not found
|
|
176
|
+
"""
|
|
177
|
+
# Check cache first
|
|
178
|
+
if self._framework_path is not None:
|
|
179
|
+
return self._framework_path
|
|
180
|
+
|
|
181
|
+
# Try to use internal cache if available
|
|
182
|
+
if "framework_path" in self._path_cache:
|
|
183
|
+
cached_path = self._path_cache["framework_path"]
|
|
184
|
+
self._framework_path = (
|
|
185
|
+
Path(cached_path)
|
|
186
|
+
if cached_path != "__PACKAGED__"
|
|
187
|
+
else Path("__PACKAGED__")
|
|
188
|
+
)
|
|
189
|
+
return self._framework_path
|
|
190
|
+
|
|
191
|
+
# Try unified path manager first
|
|
192
|
+
framework_path = self._detect_via_unified_paths()
|
|
193
|
+
if framework_path:
|
|
194
|
+
self._cache_framework_path(framework_path)
|
|
195
|
+
return framework_path
|
|
196
|
+
|
|
197
|
+
# Fallback to package detection
|
|
198
|
+
framework_path = self._detect_via_package()
|
|
199
|
+
if framework_path:
|
|
200
|
+
self._cache_framework_path(framework_path)
|
|
201
|
+
return framework_path
|
|
202
|
+
|
|
203
|
+
# Try development mode detection
|
|
204
|
+
framework_path = self._detect_development_mode()
|
|
205
|
+
if framework_path:
|
|
206
|
+
self._cache_framework_path(framework_path)
|
|
207
|
+
return framework_path
|
|
208
|
+
|
|
209
|
+
# Check common locations
|
|
210
|
+
framework_path = self._check_common_locations()
|
|
211
|
+
if framework_path:
|
|
212
|
+
self._cache_framework_path(framework_path)
|
|
213
|
+
return framework_path
|
|
214
|
+
|
|
215
|
+
self.logger.warning("Framework not found, will use minimal instructions")
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def get_npm_global_path(self) -> Optional[Path]:
|
|
219
|
+
"""
|
|
220
|
+
Get npm global installation path for @bobmatnyc/claude-multiagent-pm.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Path to npm global installation or None if not found
|
|
224
|
+
"""
|
|
225
|
+
# Check internal cache first
|
|
226
|
+
if "npm_global_path" in self._path_cache:
|
|
227
|
+
cached_path = self._path_cache["npm_global_path"]
|
|
228
|
+
return Path(cached_path) if cached_path != "NOT_FOUND" else None
|
|
229
|
+
|
|
230
|
+
npm_path = self._detect_npm_global()
|
|
231
|
+
|
|
232
|
+
# Cache the result internally
|
|
233
|
+
cache_value = str(npm_path) if npm_path else "NOT_FOUND"
|
|
234
|
+
self._path_cache["npm_global_path"] = cache_value
|
|
235
|
+
|
|
236
|
+
return npm_path
|
|
237
|
+
|
|
238
|
+
def get_deployment_context(self) -> DeploymentContext:
|
|
239
|
+
"""
|
|
240
|
+
Get the current deployment context.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
The detected deployment context
|
|
244
|
+
"""
|
|
245
|
+
if self._deployment_context is None:
|
|
246
|
+
self._deployment_context = self._detect_deployment_context()
|
|
247
|
+
return self._deployment_context
|
|
248
|
+
|
|
249
|
+
def discover_agent_paths(
|
|
250
|
+
self, agents_dir: Optional[Path] = None, framework_path: Optional[Path] = None
|
|
251
|
+
) -> Tuple[Optional[Path], Optional[Path], Optional[Path]]:
|
|
252
|
+
"""
|
|
253
|
+
Discover agent directories based on priority.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
agents_dir: Custom agents directory override
|
|
257
|
+
framework_path: Framework path to search in
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Tuple of (agents_dir, templates_dir, main_dir)
|
|
261
|
+
"""
|
|
262
|
+
discovered_agents_dir = None
|
|
263
|
+
templates_dir = None
|
|
264
|
+
main_dir = None
|
|
265
|
+
|
|
266
|
+
if agents_dir and agents_dir.exists():
|
|
267
|
+
discovered_agents_dir = agents_dir
|
|
268
|
+
self.logger.info(f"Using custom agents directory: {discovered_agents_dir}")
|
|
269
|
+
elif framework_path and framework_path != Path("__PACKAGED__"):
|
|
270
|
+
# Prioritize templates directory over main agents directory
|
|
271
|
+
templates_dir = (
|
|
272
|
+
framework_path / "src" / "claude_mpm" / "agents" / "templates"
|
|
273
|
+
)
|
|
274
|
+
main_dir = framework_path / "src" / "claude_mpm" / "agents"
|
|
275
|
+
|
|
276
|
+
if templates_dir.exists() and any(templates_dir.glob("*.md")):
|
|
277
|
+
discovered_agents_dir = templates_dir
|
|
278
|
+
self.logger.info(
|
|
279
|
+
f"Using agents from templates directory: {discovered_agents_dir}"
|
|
280
|
+
)
|
|
281
|
+
elif main_dir.exists() and any(main_dir.glob("*.md")):
|
|
282
|
+
discovered_agents_dir = main_dir
|
|
283
|
+
self.logger.info(
|
|
284
|
+
f"Using agents from main directory: {discovered_agents_dir}"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return discovered_agents_dir, templates_dir, main_dir
|
|
288
|
+
|
|
289
|
+
def get_instruction_file_paths(self) -> Dict[str, Optional[Path]]:
|
|
290
|
+
"""
|
|
291
|
+
Get paths for instruction files with precedence.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Dictionary mapping instruction type to path:
|
|
295
|
+
- "project": Project-specific INSTRUCTIONS.md
|
|
296
|
+
- "user": User-specific INSTRUCTIONS.md
|
|
297
|
+
- "system": System-wide INSTRUCTIONS.md
|
|
298
|
+
"""
|
|
299
|
+
paths = {"project": None, "user": None, "system": None}
|
|
300
|
+
|
|
301
|
+
# Project-specific instructions
|
|
302
|
+
project_path = Path.cwd() / ".claude-mpm" / "INSTRUCTIONS.md"
|
|
303
|
+
if project_path.exists():
|
|
304
|
+
paths["project"] = project_path
|
|
305
|
+
|
|
306
|
+
# User-specific instructions
|
|
307
|
+
user_path = Path.home() / ".claude-mpm" / "INSTRUCTIONS.md"
|
|
308
|
+
if user_path.exists():
|
|
309
|
+
paths["user"] = user_path
|
|
310
|
+
|
|
311
|
+
# System-wide instructions (if framework is detected)
|
|
312
|
+
framework_path = self.detect_framework_path()
|
|
313
|
+
if framework_path and framework_path != Path("__PACKAGED__"):
|
|
314
|
+
system_path = (
|
|
315
|
+
framework_path / "src" / "claude_mpm" / "agents" / "INSTRUCTIONS.md"
|
|
316
|
+
)
|
|
317
|
+
if system_path.exists():
|
|
318
|
+
paths["system"] = system_path
|
|
319
|
+
|
|
320
|
+
return paths
|
|
321
|
+
|
|
322
|
+
# Private helper methods
|
|
323
|
+
|
|
324
|
+
def _detect_via_unified_paths(self) -> Optional[Path]:
|
|
325
|
+
"""Detect framework path using unified path management."""
|
|
326
|
+
try:
|
|
327
|
+
# Import here to avoid circular dependencies
|
|
328
|
+
from ...core.unified_paths import (
|
|
329
|
+
DeploymentContext as UnifiedContext,
|
|
330
|
+
get_path_manager,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
path_manager = get_path_manager()
|
|
334
|
+
deployment_context = path_manager._deployment_context
|
|
335
|
+
|
|
336
|
+
# Map unified context to our context
|
|
337
|
+
context_map = {
|
|
338
|
+
UnifiedContext.PIP_INSTALL: DeploymentContext.PIP_INSTALL,
|
|
339
|
+
UnifiedContext.PIPX_INSTALL: DeploymentContext.PIPX_INSTALL,
|
|
340
|
+
UnifiedContext.SYSTEM_PACKAGE: DeploymentContext.SYSTEM_PACKAGE,
|
|
341
|
+
UnifiedContext.DEVELOPMENT: DeploymentContext.DEVELOPMENT,
|
|
342
|
+
UnifiedContext.EDITABLE_INSTALL: DeploymentContext.EDITABLE_INSTALL,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if deployment_context in context_map:
|
|
346
|
+
self._deployment_context = context_map[deployment_context]
|
|
347
|
+
|
|
348
|
+
# Check if we're in a packaged installation
|
|
349
|
+
if deployment_context in [
|
|
350
|
+
UnifiedContext.PIP_INSTALL,
|
|
351
|
+
UnifiedContext.PIPX_INSTALL,
|
|
352
|
+
UnifiedContext.SYSTEM_PACKAGE,
|
|
353
|
+
]:
|
|
354
|
+
self.logger.info(
|
|
355
|
+
f"Running from packaged installation (context: {deployment_context})"
|
|
356
|
+
)
|
|
357
|
+
return Path("__PACKAGED__")
|
|
358
|
+
|
|
359
|
+
if deployment_context == UnifiedContext.DEVELOPMENT:
|
|
360
|
+
# Development mode - use framework root
|
|
361
|
+
framework_root = path_manager.framework_root
|
|
362
|
+
if (framework_root / "src" / "claude_mpm" / "agents").exists():
|
|
363
|
+
self.logger.info(
|
|
364
|
+
f"Using claude-mpm development installation at: {framework_root}"
|
|
365
|
+
)
|
|
366
|
+
return framework_root
|
|
367
|
+
|
|
368
|
+
elif deployment_context == UnifiedContext.EDITABLE_INSTALL:
|
|
369
|
+
# Editable install - similar to development
|
|
370
|
+
framework_root = path_manager.framework_root
|
|
371
|
+
if (framework_root / "src" / "claude_mpm" / "agents").exists():
|
|
372
|
+
self.logger.info(
|
|
373
|
+
f"Using claude-mpm editable installation at: {framework_root}"
|
|
374
|
+
)
|
|
375
|
+
return framework_root
|
|
376
|
+
|
|
377
|
+
except Exception as e:
|
|
378
|
+
self.logger.warning(
|
|
379
|
+
f"Failed to use unified path manager for framework detection: {e}"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
return None
|
|
383
|
+
|
|
384
|
+
def _detect_via_package(self) -> Optional[Path]:
|
|
385
|
+
"""Detect framework via package installation."""
|
|
386
|
+
try:
|
|
387
|
+
import claude_mpm
|
|
388
|
+
|
|
389
|
+
package_file = Path(claude_mpm.__file__)
|
|
390
|
+
|
|
391
|
+
# For packaged installations, we don't need a framework path
|
|
392
|
+
# since we'll use importlib.resources to load files
|
|
393
|
+
if "site-packages" in str(package_file) or "dist-packages" in str(
|
|
394
|
+
package_file
|
|
395
|
+
):
|
|
396
|
+
self.logger.info(
|
|
397
|
+
f"Running from packaged installation at: {package_file.parent}"
|
|
398
|
+
)
|
|
399
|
+
self._deployment_context = DeploymentContext.PIP_INSTALL
|
|
400
|
+
return Path("__PACKAGED__")
|
|
401
|
+
except ImportError:
|
|
402
|
+
pass
|
|
403
|
+
|
|
404
|
+
return None
|
|
405
|
+
|
|
406
|
+
def _detect_development_mode(self) -> Optional[Path]:
|
|
407
|
+
"""Detect if running in development mode."""
|
|
408
|
+
current_file = Path(__file__)
|
|
409
|
+
|
|
410
|
+
if "claude-mpm" in str(current_file):
|
|
411
|
+
# We're running from claude-mpm, use its agents
|
|
412
|
+
for parent in current_file.parents:
|
|
413
|
+
if parent.name == "claude-mpm":
|
|
414
|
+
if (parent / "src" / "claude_mpm" / "agents").exists():
|
|
415
|
+
self.logger.info(f"Using claude-mpm at: {parent}")
|
|
416
|
+
self._deployment_context = DeploymentContext.DEVELOPMENT
|
|
417
|
+
return parent
|
|
418
|
+
break
|
|
419
|
+
|
|
420
|
+
return None
|
|
421
|
+
|
|
422
|
+
def _check_common_locations(self) -> Optional[Path]:
|
|
423
|
+
"""Check common locations for claude-mpm."""
|
|
424
|
+
candidates = [
|
|
425
|
+
# Current directory (if we're already in claude-mpm)
|
|
426
|
+
Path.cwd(),
|
|
427
|
+
# Development location
|
|
428
|
+
Path.home() / "Projects" / "claude-mpm",
|
|
429
|
+
# Current directory subdirectory
|
|
430
|
+
Path.cwd() / "claude-mpm",
|
|
431
|
+
]
|
|
432
|
+
|
|
433
|
+
for candidate in candidates:
|
|
434
|
+
if candidate and candidate.exists():
|
|
435
|
+
# Check for claude-mpm agents directory
|
|
436
|
+
if (candidate / "src" / "claude_mpm" / "agents").exists():
|
|
437
|
+
self.logger.info(f"Found claude-mpm at: {candidate}")
|
|
438
|
+
return candidate
|
|
439
|
+
|
|
440
|
+
return None
|
|
441
|
+
|
|
442
|
+
def _detect_npm_global(self) -> Optional[Path]:
|
|
443
|
+
"""Detect npm global installation path."""
|
|
444
|
+
try:
|
|
445
|
+
result = subprocess.run(
|
|
446
|
+
["npm", "root", "-g"],
|
|
447
|
+
capture_output=True,
|
|
448
|
+
text=True,
|
|
449
|
+
timeout=5,
|
|
450
|
+
check=False,
|
|
451
|
+
)
|
|
452
|
+
if result.returncode == 0:
|
|
453
|
+
npm_root = Path(result.stdout.strip())
|
|
454
|
+
npm_path = npm_root / "@bobmatnyc" / "claude-multiagent-pm"
|
|
455
|
+
if npm_path.exists():
|
|
456
|
+
return npm_path
|
|
457
|
+
except (subprocess.SubprocessError, OSError, FileNotFoundError):
|
|
458
|
+
pass
|
|
459
|
+
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
def _detect_deployment_context(self) -> DeploymentContext:
|
|
463
|
+
"""Detect the current deployment context."""
|
|
464
|
+
# If already detected via framework path detection
|
|
465
|
+
if self._deployment_context:
|
|
466
|
+
return self._deployment_context
|
|
467
|
+
|
|
468
|
+
# Try to detect based on current environment
|
|
469
|
+
try:
|
|
470
|
+
import claude_mpm
|
|
471
|
+
|
|
472
|
+
package_file = Path(claude_mpm.__file__)
|
|
473
|
+
package_str = str(package_file)
|
|
474
|
+
|
|
475
|
+
# Check for pipx first (more specific)
|
|
476
|
+
if ".local" in package_str and "pipx" in package_str:
|
|
477
|
+
return DeploymentContext.PIPX_INSTALL
|
|
478
|
+
if "dist-packages" in package_str:
|
|
479
|
+
return DeploymentContext.SYSTEM_PACKAGE
|
|
480
|
+
if "site-packages" in package_str:
|
|
481
|
+
return DeploymentContext.PIP_INSTALL
|
|
482
|
+
|
|
483
|
+
except ImportError:
|
|
484
|
+
pass
|
|
485
|
+
|
|
486
|
+
# Check if we're in development
|
|
487
|
+
if (Path.cwd() / "pyproject.toml").exists():
|
|
488
|
+
return DeploymentContext.DEVELOPMENT
|
|
489
|
+
|
|
490
|
+
return DeploymentContext.UNKNOWN
|
|
491
|
+
|
|
492
|
+
def _cache_framework_path(self, path: Path) -> None:
|
|
493
|
+
"""Cache the framework path."""
|
|
494
|
+
self._framework_path = path
|
|
495
|
+
|
|
496
|
+
# Cache internally
|
|
497
|
+
cache_value = str(path) if path != Path("__PACKAGED__") else "__PACKAGED__"
|
|
498
|
+
self._path_cache["framework_path"] = cache_value
|