claude-mpm 5.4.3__py3-none-any.whl → 5.4.14__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +4 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +39 -0
- claude_mpm/agents/agent_loader.py +3 -27
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +85 -43
- claude_mpm/cli/commands/configure_agent_display.py +3 -1
- claude_mpm/cli/commands/mpm_init/core.py +2 -45
- claude_mpm/cli/commands/skills.py +21 -2
- claude_mpm/cli/executor.py +3 -3
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +273 -36
- claude_mpm/commands/mpm-config.md +266 -0
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +171 -76
- claude_mpm/hooks/claude_hooks/hook_handler.py +2 -0
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +0 -19
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/services/agents/agent_recommendation_service.py +6 -7
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
- claude_mpm/services/agents/deployment/agent_template_builder.py +1 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +13 -0
- claude_mpm/services/agents/git_source_manager.py +14 -0
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/toolchain_detector.py +6 -3
- claude_mpm/services/command_deployment_service.py +71 -8
- claude_mpm/services/git/git_operations_service.py +93 -8
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +32 -2
- claude_mpm/services/skills/selective_skill_deployer.py +230 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +64 -3
- {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.14.dist-info}/METADATA +47 -8
- {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.14.dist-info}/RECORD +51 -70
- {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.14.dist-info}/entry_points.txt +0 -3
- claude_mpm-5.4.14.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.14.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_ENGINEER.md +0 -658
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent.json +0 -31
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm-5.4.3.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.14.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.14.dist-info}/top_level.txt +0 -0
|
@@ -160,11 +160,13 @@ class ToolchainDetector:
|
|
|
160
160
|
|
|
161
161
|
# Core agents always included (use exact agent IDs from repository)
|
|
162
162
|
CORE_AGENTS = [
|
|
163
|
+
"engineer",
|
|
163
164
|
"qa-agent",
|
|
165
|
+
"memory-manager-agent",
|
|
166
|
+
"local-ops-agent",
|
|
164
167
|
"research-agent",
|
|
165
168
|
"documentation-agent",
|
|
166
|
-
"
|
|
167
|
-
"local-ops-agent",
|
|
169
|
+
"security-agent",
|
|
168
170
|
]
|
|
169
171
|
|
|
170
172
|
# Directories to exclude from scanning
|
|
@@ -366,7 +368,8 @@ class ToolchainDetector:
|
|
|
366
368
|
"""Map detected toolchain to recommended agents.
|
|
367
369
|
|
|
368
370
|
Combines language-specific, framework-specific, and ops agents with
|
|
369
|
-
core agents (
|
|
371
|
+
core agents (engineer, qa-agent, memory-manager-agent, local-ops-agent, research-agent,
|
|
372
|
+
documentation-agent, security-agent).
|
|
370
373
|
|
|
371
374
|
Args:
|
|
372
375
|
toolchain: Detected toolchain dictionary with languages, frameworks, tools
|
|
@@ -23,7 +23,7 @@ class CommandDeploymentService(BaseService):
|
|
|
23
23
|
DEPRECATED_COMMANDS = [
|
|
24
24
|
"mpm-agents.md", # Replaced by mpm-agents-list.md
|
|
25
25
|
"mpm-auto-configure.md", # Replaced by mpm-agents-auto-configure.md
|
|
26
|
-
"mpm-config.md", # Replaced by mpm-config
|
|
26
|
+
"mpm-config-view.md", # Replaced by mpm-config.md
|
|
27
27
|
"mpm-resume.md", # Replaced by mpm-session-resume.md
|
|
28
28
|
"mpm-ticket.md", # Replaced by mpm-ticket-view.md
|
|
29
29
|
]
|
|
@@ -314,7 +314,7 @@ class CommandDeploymentService(BaseService):
|
|
|
314
314
|
replacement_map = {
|
|
315
315
|
"mpm-agents.md": "mpm-agents-list.md",
|
|
316
316
|
"mpm-auto-configure.md": "mpm-agents-auto-configure.md",
|
|
317
|
-
"mpm-config.md": "mpm-config
|
|
317
|
+
"mpm-config-view.md": "mpm-config.md",
|
|
318
318
|
"mpm-resume.md": "mpm-session-resume.md",
|
|
319
319
|
"mpm-ticket.md": "mpm-ticket-view.md",
|
|
320
320
|
}
|
|
@@ -343,13 +343,71 @@ class CommandDeploymentService(BaseService):
|
|
|
343
343
|
|
|
344
344
|
return removed
|
|
345
345
|
|
|
346
|
+
def remove_stale_commands(self) -> int:
|
|
347
|
+
"""Remove stale MPM commands that no longer exist in source.
|
|
348
|
+
|
|
349
|
+
This method cleans up deployed commands that have been deleted or renamed
|
|
350
|
+
in the source directory. It's called automatically on startup to ensure
|
|
351
|
+
deployed commands stay in sync with source.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Number of stale files removed
|
|
355
|
+
"""
|
|
356
|
+
if not self.target_dir.exists():
|
|
357
|
+
self.logger.debug(
|
|
358
|
+
f"Target directory does not exist: {self.target_dir}, skipping stale command cleanup"
|
|
359
|
+
)
|
|
360
|
+
return 0
|
|
361
|
+
|
|
362
|
+
if not self.source_dir.exists():
|
|
363
|
+
self.logger.warning(
|
|
364
|
+
f"Source directory does not exist: {self.source_dir}, cannot detect stale commands"
|
|
365
|
+
)
|
|
366
|
+
return 0
|
|
367
|
+
|
|
368
|
+
# Get current source commands (ground truth)
|
|
369
|
+
source_commands = {f.name for f in self.source_dir.glob("mpm*.md")}
|
|
370
|
+
|
|
371
|
+
# Get deployed commands
|
|
372
|
+
deployed_commands = {f.name for f in self.target_dir.glob("mpm*.md")}
|
|
373
|
+
|
|
374
|
+
# Find stale commands (deployed but not in source, excluding deprecated)
|
|
375
|
+
stale_commands = (
|
|
376
|
+
deployed_commands - source_commands - set(self.DEPRECATED_COMMANDS)
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if not stale_commands:
|
|
380
|
+
self.logger.debug("No stale commands found to remove")
|
|
381
|
+
return 0
|
|
382
|
+
|
|
383
|
+
removed = 0
|
|
384
|
+
self.logger.info(f"Cleaning up {len(stale_commands)} stale command(s)...")
|
|
385
|
+
|
|
386
|
+
for stale_cmd in stale_commands:
|
|
387
|
+
stale_file = self.target_dir / stale_cmd
|
|
388
|
+
try:
|
|
389
|
+
stale_file.unlink()
|
|
390
|
+
self.logger.info(
|
|
391
|
+
f"Removed stale command: {stale_cmd} (no longer in source)"
|
|
392
|
+
)
|
|
393
|
+
removed += 1
|
|
394
|
+
except Exception as e:
|
|
395
|
+
# Log error but don't fail startup - this is non-critical
|
|
396
|
+
self.logger.warning(f"Failed to remove stale command {stale_cmd}: {e}")
|
|
397
|
+
|
|
398
|
+
if removed > 0:
|
|
399
|
+
self.logger.info(f"Removed {removed} stale command(s)")
|
|
400
|
+
|
|
401
|
+
return removed
|
|
402
|
+
|
|
346
403
|
|
|
347
404
|
def deploy_commands_on_startup(force: bool = False) -> None:
|
|
348
405
|
"""Convenience function to deploy commands during startup.
|
|
349
406
|
|
|
350
407
|
This function:
|
|
351
408
|
1. Removes deprecated commands that have been replaced
|
|
352
|
-
2.
|
|
409
|
+
2. Removes stale commands that no longer exist in source
|
|
410
|
+
3. Deploys current command files
|
|
353
411
|
|
|
354
412
|
Args:
|
|
355
413
|
force: Force deployment even if files exist
|
|
@@ -357,12 +415,17 @@ def deploy_commands_on_startup(force: bool = False) -> None:
|
|
|
357
415
|
service = CommandDeploymentService()
|
|
358
416
|
logger = get_logger("startup")
|
|
359
417
|
|
|
360
|
-
# Clean up deprecated commands
|
|
361
|
-
|
|
362
|
-
if
|
|
363
|
-
logger.info(f"Cleaned up {
|
|
418
|
+
# Clean up deprecated commands FIRST (known old commands)
|
|
419
|
+
deprecated_count = service.remove_deprecated_commands()
|
|
420
|
+
if deprecated_count > 0:
|
|
421
|
+
logger.info(f"Cleaned up {deprecated_count} deprecated command(s)")
|
|
422
|
+
|
|
423
|
+
# Clean up stale commands SECOND (deployed but not in source anymore)
|
|
424
|
+
stale_count = service.remove_stale_commands()
|
|
425
|
+
if stale_count > 0:
|
|
426
|
+
logger.info(f"Cleaned up {stale_count} stale command(s)")
|
|
364
427
|
|
|
365
|
-
# Deploy current commands
|
|
428
|
+
# Deploy current commands LAST
|
|
366
429
|
result = service.deploy_commands(force=force)
|
|
367
430
|
|
|
368
431
|
if result["deployed"]:
|
|
@@ -18,10 +18,13 @@ Example:
|
|
|
18
18
|
... service.commit(Path("~/.claude-mpm/cache/remote-agents"), "feat: improve research agent memory handling")
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
import logging
|
|
21
22
|
import subprocess
|
|
22
23
|
from pathlib import Path
|
|
23
24
|
from typing import List, Optional, Tuple
|
|
24
25
|
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
# Custom Exceptions
|
|
27
30
|
class GitOperationError(Exception):
|
|
@@ -314,7 +317,12 @@ class GitOperationsService:
|
|
|
314
317
|
|
|
315
318
|
def pull(self, repo_path: Path, branch: str = "main") -> bool:
|
|
316
319
|
"""
|
|
317
|
-
Pull latest changes from remote.
|
|
320
|
+
Pull latest changes from remote with automatic divergent branch recovery.
|
|
321
|
+
|
|
322
|
+
For cache/sync repositories, automatically handles divergent branches by:
|
|
323
|
+
1. First attempting git pull --rebase (cleaner history)
|
|
324
|
+
2. If rebase fails or conflicts exist, hard reset to match remote exactly
|
|
325
|
+
(cache repos should mirror remote state, local changes are discarded)
|
|
318
326
|
|
|
319
327
|
Args:
|
|
320
328
|
repo_path: Repository path
|
|
@@ -325,21 +333,98 @@ class GitOperationsService:
|
|
|
325
333
|
|
|
326
334
|
Raises:
|
|
327
335
|
GitOperationError: If pull fails
|
|
328
|
-
GitConflictError: If merge conflicts detected
|
|
336
|
+
GitConflictError: If merge conflicts detected and cannot be auto-resolved
|
|
329
337
|
"""
|
|
330
338
|
self._validate_repo(repo_path)
|
|
331
339
|
|
|
340
|
+
# First, try standard pull
|
|
332
341
|
returncode, _stdout, stderr = self._run_git_command(
|
|
333
342
|
["git", "pull", "origin", branch], cwd=repo_path
|
|
334
343
|
)
|
|
335
344
|
|
|
336
|
-
if returncode
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
345
|
+
if returncode == 0:
|
|
346
|
+
return True
|
|
347
|
+
|
|
348
|
+
# Check if this is a divergent branch situation
|
|
349
|
+
is_divergent = any(
|
|
350
|
+
phrase in stderr.lower()
|
|
351
|
+
for phrase in [
|
|
352
|
+
"divergent branches",
|
|
353
|
+
"need to specify how to reconcile",
|
|
354
|
+
"have diverged",
|
|
355
|
+
]
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
if is_divergent:
|
|
359
|
+
logger.warning(
|
|
360
|
+
f"Divergent branches detected in cache repository at {repo_path}. "
|
|
361
|
+
"Attempting automatic recovery with rebase..."
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Strategy 1: Try rebase for cleaner history
|
|
365
|
+
returncode, _stdout, stderr = self._run_git_command(
|
|
366
|
+
["git", "pull", "--rebase", "origin", branch], cwd=repo_path
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if returncode == 0:
|
|
370
|
+
logger.info(
|
|
371
|
+
f"Successfully resolved divergent branches with rebase for {branch}"
|
|
372
|
+
)
|
|
373
|
+
return True
|
|
374
|
+
|
|
375
|
+
# Check if rebase had conflicts
|
|
376
|
+
has_rebase_conflict = any(
|
|
377
|
+
phrase in stderr.lower()
|
|
378
|
+
for phrase in ["conflict", "rebase in progress"]
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if has_rebase_conflict:
|
|
382
|
+
logger.warning(
|
|
383
|
+
"Rebase conflicts detected. Aborting rebase and resetting to remote state..."
|
|
341
384
|
)
|
|
342
|
-
|
|
385
|
+
# Abort the rebase
|
|
386
|
+
self._run_git_command(["git", "rebase", "--abort"], cwd=repo_path)
|
|
387
|
+
|
|
388
|
+
# Strategy 2: Hard reset to match remote exactly (cache repos should mirror remote)
|
|
389
|
+
logger.warning(
|
|
390
|
+
f"Discarding local changes and resetting to origin/{branch} "
|
|
391
|
+
"(cache repositories should match remote exactly)"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Fetch latest to ensure we have the remote state
|
|
395
|
+
returncode, _stdout, stderr = self._run_git_command(
|
|
396
|
+
["git", "fetch", "origin", branch], cwd=repo_path
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
if returncode != 0:
|
|
400
|
+
raise GitOperationError(
|
|
401
|
+
f"Failed to fetch from remote during divergent branch recovery: {stderr}"
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Hard reset to remote branch
|
|
405
|
+
returncode, _stdout, stderr = self._run_git_command(
|
|
406
|
+
["git", "reset", "--hard", f"origin/{branch}"], cwd=repo_path
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
if returncode != 0:
|
|
410
|
+
raise GitOperationError(
|
|
411
|
+
f"Failed to reset to origin/{branch} during divergent branch recovery: {stderr}"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
logger.info(
|
|
415
|
+
f"Successfully reset cache repository to origin/{branch} "
|
|
416
|
+
"(local changes discarded)"
|
|
417
|
+
)
|
|
418
|
+
return True
|
|
419
|
+
|
|
420
|
+
# Check for merge conflicts (non-divergent case)
|
|
421
|
+
if "conflict" in stderr.lower():
|
|
422
|
+
raise GitConflictError(
|
|
423
|
+
f"Merge conflicts detected when pulling {branch}: {stderr}"
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Other pull errors
|
|
427
|
+
raise GitOperationError(f"Failed to pull {branch}: {stderr}")
|
|
343
428
|
|
|
344
429
|
return True
|
|
345
430
|
|
|
@@ -3,7 +3,7 @@ Self-Upgrade Service
|
|
|
3
3
|
====================
|
|
4
4
|
|
|
5
5
|
Handles version checking and self-upgrade functionality for claude-mpm.
|
|
6
|
-
Supports pip, pipx, and
|
|
6
|
+
Supports pip, pipx, npm, uv tool, and Homebrew installations with automatic detection.
|
|
7
7
|
Also checks Claude Code version compatibility.
|
|
8
8
|
|
|
9
9
|
WHY: Users should be notified of updates and have an easy way to upgrade
|
|
@@ -11,7 +11,7 @@ without manually running installation commands. Claude Code version checking
|
|
|
11
11
|
ensures compatibility with required features.
|
|
12
12
|
|
|
13
13
|
DESIGN DECISIONS:
|
|
14
|
-
- Detects installation method (pip/pipx/npm/editable)
|
|
14
|
+
- Detects installation method (pip/pipx/npm/uv_tool/homebrew/editable)
|
|
15
15
|
- Non-blocking version checks with caching
|
|
16
16
|
- Interactive upgrade prompts with confirmation
|
|
17
17
|
- Automatic restart after upgrade
|
|
@@ -40,6 +40,8 @@ class InstallationMethod:
|
|
|
40
40
|
PIP = "pip"
|
|
41
41
|
PIPX = "pipx"
|
|
42
42
|
NPM = "npm"
|
|
43
|
+
UV_TOOL = "uv_tool"
|
|
44
|
+
HOMEBREW = "homebrew"
|
|
43
45
|
EDITABLE = "editable"
|
|
44
46
|
UNKNOWN = "unknown"
|
|
45
47
|
|
|
@@ -95,6 +97,14 @@ class SelfUpgradeService:
|
|
|
95
97
|
"""
|
|
96
98
|
Detect how claude-mpm was installed.
|
|
97
99
|
|
|
100
|
+
Detection priority:
|
|
101
|
+
1. Editable (skip auto-upgrade)
|
|
102
|
+
2. UV Tool
|
|
103
|
+
3. Homebrew
|
|
104
|
+
4. Pipx
|
|
105
|
+
5. NPM
|
|
106
|
+
6. Pip (default)
|
|
107
|
+
|
|
98
108
|
Returns:
|
|
99
109
|
Installation method constant
|
|
100
110
|
"""
|
|
@@ -105,6 +115,14 @@ class SelfUpgradeService:
|
|
|
105
115
|
]:
|
|
106
116
|
return InstallationMethod.EDITABLE
|
|
107
117
|
|
|
118
|
+
# Check for UV tool installation
|
|
119
|
+
if self._check_uv_tool_installation():
|
|
120
|
+
return InstallationMethod.UV_TOOL
|
|
121
|
+
|
|
122
|
+
# Check for Homebrew installation
|
|
123
|
+
if self._check_homebrew_installation():
|
|
124
|
+
return InstallationMethod.HOMEBREW
|
|
125
|
+
|
|
108
126
|
# Check for pipx by looking at executable path
|
|
109
127
|
executable = sys.executable
|
|
110
128
|
if "pipx" in executable:
|
|
@@ -127,6 +145,85 @@ class SelfUpgradeService:
|
|
|
127
145
|
# Default to pip
|
|
128
146
|
return InstallationMethod.PIP
|
|
129
147
|
|
|
148
|
+
def _check_uv_tool_installation(self) -> bool:
|
|
149
|
+
"""
|
|
150
|
+
Check if claude-mpm is installed via uv tool.
|
|
151
|
+
|
|
152
|
+
Detection methods:
|
|
153
|
+
1. Check UV_TOOL_DIR environment variable
|
|
154
|
+
2. Check if executable path contains .local/share/uv/tools/
|
|
155
|
+
3. Fallback: Run `uv tool list` and check for claude-mpm
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
True if UV tool installation detected
|
|
159
|
+
"""
|
|
160
|
+
# Method 1: Check UV_TOOL_DIR environment variable
|
|
161
|
+
uv_tool_dir = os.environ.get("UV_TOOL_DIR")
|
|
162
|
+
if uv_tool_dir and "claude-mpm" in uv_tool_dir:
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
# Method 2: Check executable path
|
|
166
|
+
executable = sys.executable
|
|
167
|
+
if ".local/share/uv/tools/" in executable or "uv/tools/" in executable:
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
# Method 3: Fallback to `uv tool list` command
|
|
171
|
+
try:
|
|
172
|
+
result = subprocess.run(
|
|
173
|
+
["uv", "tool", "list"],
|
|
174
|
+
check=False,
|
|
175
|
+
capture_output=True,
|
|
176
|
+
text=True,
|
|
177
|
+
timeout=5,
|
|
178
|
+
)
|
|
179
|
+
if result.returncode == 0 and "claude-mpm" in result.stdout:
|
|
180
|
+
return True
|
|
181
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
182
|
+
# uv not installed or command failed
|
|
183
|
+
pass
|
|
184
|
+
except Exception as e:
|
|
185
|
+
self.logger.debug(f"UV tool check failed: {e}")
|
|
186
|
+
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
def _check_homebrew_installation(self) -> bool:
|
|
190
|
+
"""
|
|
191
|
+
Check if claude-mpm is installed via Homebrew.
|
|
192
|
+
|
|
193
|
+
Detection methods:
|
|
194
|
+
1. Check if executable path starts with /opt/homebrew/ or /usr/local/Cellar/
|
|
195
|
+
2. Fallback: Run `brew list claude-mpm` to verify
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
True if Homebrew installation detected
|
|
199
|
+
"""
|
|
200
|
+
# Method 1: Check executable path
|
|
201
|
+
executable = sys.executable
|
|
202
|
+
if executable.startswith("/opt/homebrew/") or executable.startswith(
|
|
203
|
+
"/usr/local/Cellar/"
|
|
204
|
+
):
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
# Method 2: Fallback to `brew list` command
|
|
208
|
+
try:
|
|
209
|
+
result = subprocess.run(
|
|
210
|
+
["brew", "list", "claude-mpm"],
|
|
211
|
+
check=False,
|
|
212
|
+
capture_output=True,
|
|
213
|
+
text=True,
|
|
214
|
+
timeout=5,
|
|
215
|
+
)
|
|
216
|
+
# brew list returns 0 if package is installed
|
|
217
|
+
if result.returncode == 0:
|
|
218
|
+
return True
|
|
219
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
220
|
+
# brew not installed or command failed
|
|
221
|
+
pass
|
|
222
|
+
except Exception as e:
|
|
223
|
+
self.logger.debug(f"Homebrew check failed: {e}")
|
|
224
|
+
|
|
225
|
+
return False
|
|
226
|
+
|
|
130
227
|
def _get_claude_code_version(self) -> Optional[str]:
|
|
131
228
|
"""
|
|
132
229
|
Get the installed Claude Code version.
|
|
@@ -257,6 +354,8 @@ class SelfUpgradeService:
|
|
|
257
354
|
if self.installation_method in [
|
|
258
355
|
InstallationMethod.PIP,
|
|
259
356
|
InstallationMethod.PIPX,
|
|
357
|
+
InstallationMethod.UV_TOOL,
|
|
358
|
+
InstallationMethod.HOMEBREW,
|
|
260
359
|
]:
|
|
261
360
|
result = await self.version_checker.check_for_update(
|
|
262
361
|
"claude-mpm", self.current_version, cache_ttl
|
|
@@ -313,15 +412,18 @@ class SelfUpgradeService:
|
|
|
313
412
|
Returns:
|
|
314
413
|
Shell command string to upgrade claude-mpm
|
|
315
414
|
"""
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
415
|
+
upgrade_commands = {
|
|
416
|
+
InstallationMethod.UV_TOOL: "uv tool upgrade claude-mpm",
|
|
417
|
+
InstallationMethod.HOMEBREW: "brew upgrade claude-mpm",
|
|
418
|
+
InstallationMethod.PIPX: "pipx upgrade claude-mpm",
|
|
419
|
+
InstallationMethod.NPM: "npm update -g claude-mpm",
|
|
420
|
+
InstallationMethod.PIP: f"{sys.executable} -m pip install --upgrade claude-mpm",
|
|
421
|
+
InstallationMethod.EDITABLE: "git pull && pip install -e .",
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return upgrade_commands.get(
|
|
425
|
+
self.installation_method, "pip install --upgrade claude-mpm"
|
|
426
|
+
)
|
|
325
427
|
|
|
326
428
|
def prompt_for_upgrade(self, update_info: Dict[str, any]) -> bool:
|
|
327
429
|
"""
|
|
@@ -432,7 +534,13 @@ class SelfUpgradeService:
|
|
|
432
534
|
args = sys.argv[:]
|
|
433
535
|
|
|
434
536
|
# Replace current process with new one
|
|
435
|
-
if self.installation_method == InstallationMethod.
|
|
537
|
+
if self.installation_method == InstallationMethod.UV_TOOL:
|
|
538
|
+
# Use uv run
|
|
539
|
+
os.execvp("uv", ["uv", "tool", "run", "claude-mpm", *args[1:]])
|
|
540
|
+
elif self.installation_method == InstallationMethod.HOMEBREW:
|
|
541
|
+
# Use direct executable (installed to PATH by Homebrew)
|
|
542
|
+
os.execvp("claude-mpm", args)
|
|
543
|
+
elif self.installation_method == InstallationMethod.PIPX:
|
|
436
544
|
# Use pipx run
|
|
437
545
|
os.execvp("pipx", ["pipx", "run", "claude-mpm", *args[1:]])
|
|
438
546
|
elif self.installation_method == InstallationMethod.NPM:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
This package provides services for Git-based skills management:
|
|
4
4
|
- GitSkillSourceManager: Multi-repository orchestration with priority resolution
|
|
5
5
|
- SkillDiscoveryService: Parse skills from Git repositories (Markdown with YAML frontmatter)
|
|
6
|
+
- SkillToAgentMapper: Map skills to agents using YAML configuration
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
from claude_mpm.services.skills.git_skill_source_manager import GitSkillSourceManager
|
|
@@ -10,9 +11,11 @@ from claude_mpm.services.skills.skill_discovery_service import (
|
|
|
10
11
|
SkillDiscoveryService,
|
|
11
12
|
SkillMetadata,
|
|
12
13
|
)
|
|
14
|
+
from claude_mpm.services.skills.skill_to_agent_mapper import SkillToAgentMapper
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
15
17
|
"GitSkillSourceManager",
|
|
16
18
|
"SkillDiscoveryService",
|
|
17
19
|
"SkillMetadata",
|
|
20
|
+
"SkillToAgentMapper",
|
|
18
21
|
]
|
|
@@ -20,7 +20,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
20
20
|
from datetime import datetime, timezone
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from threading import Lock
|
|
23
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
23
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
24
24
|
|
|
25
25
|
from claude_mpm.config.skill_sources import SkillSource, SkillSourceConfiguration
|
|
26
26
|
from claude_mpm.core.logging_config import get_logger
|
|
@@ -989,6 +989,7 @@ class GitSkillSourceManager:
|
|
|
989
989
|
target_dir: Optional[Path] = None,
|
|
990
990
|
force: bool = False,
|
|
991
991
|
progress_callback=None,
|
|
992
|
+
skill_filter: Optional[Set[str]] = None,
|
|
992
993
|
) -> Dict[str, Any]:
|
|
993
994
|
"""Deploy skills from cache to target directory with flat structure.
|
|
994
995
|
|
|
@@ -1004,6 +1005,9 @@ class GitSkillSourceManager:
|
|
|
1004
1005
|
target_dir: Target deployment directory (default: ~/.claude/skills/)
|
|
1005
1006
|
force: Overwrite existing skills
|
|
1006
1007
|
progress_callback: Optional callback(increment: int) called for each skill deployed
|
|
1008
|
+
skill_filter: Optional set of skill names to deploy (selective deployment).
|
|
1009
|
+
If None, deploys all skills. If provided, only deploys skills
|
|
1010
|
+
whose name matches an entry in the filter set.
|
|
1007
1011
|
|
|
1008
1012
|
Returns:
|
|
1009
1013
|
Dict with deployment results:
|
|
@@ -1013,13 +1017,19 @@ class GitSkillSourceManager:
|
|
|
1013
1017
|
"failed_count": int,
|
|
1014
1018
|
"deployed_skills": List[str],
|
|
1015
1019
|
"skipped_skills": List[str],
|
|
1016
|
-
"errors": List[str]
|
|
1020
|
+
"errors": List[str],
|
|
1021
|
+
"filtered_count": int # Number of skills filtered out
|
|
1017
1022
|
}
|
|
1018
1023
|
|
|
1019
1024
|
Example:
|
|
1020
1025
|
>>> manager = GitSkillSourceManager(config)
|
|
1021
1026
|
>>> result = manager.deploy_skills()
|
|
1022
1027
|
>>> print(f"Deployed {result['deployed_count']} skills")
|
|
1028
|
+
|
|
1029
|
+
# Selective deployment based on agent requirements:
|
|
1030
|
+
>>> required = {"typescript-core", "react-patterns"}
|
|
1031
|
+
>>> result = manager.deploy_skills(skill_filter=required)
|
|
1032
|
+
>>> print(f"Deployed {result['deployed_count']} of {len(required)} required skills")
|
|
1023
1033
|
"""
|
|
1024
1034
|
if target_dir is None:
|
|
1025
1035
|
target_dir = Path.home() / ".claude" / "skills"
|
|
@@ -1029,10 +1039,29 @@ class GitSkillSourceManager:
|
|
|
1029
1039
|
deployed = []
|
|
1030
1040
|
skipped = []
|
|
1031
1041
|
errors = []
|
|
1042
|
+
filtered_count = 0
|
|
1032
1043
|
|
|
1033
1044
|
# Get all skills from all sources
|
|
1034
1045
|
all_skills = self.get_all_skills()
|
|
1035
1046
|
|
|
1047
|
+
# Apply skill filter if provided (selective deployment)
|
|
1048
|
+
if skill_filter is not None:
|
|
1049
|
+
original_count = len(all_skills)
|
|
1050
|
+
# Normalize filter to lowercase for case-insensitive matching
|
|
1051
|
+
normalized_filter = {s.lower() for s in skill_filter}
|
|
1052
|
+
# Match against deployment_name (not display name) since skill_filter contains
|
|
1053
|
+
# deployment-style names like "toolchains-python-frameworks-django"
|
|
1054
|
+
all_skills = [
|
|
1055
|
+
s
|
|
1056
|
+
for s in all_skills
|
|
1057
|
+
if s.get("deployment_name", "").lower() in normalized_filter
|
|
1058
|
+
]
|
|
1059
|
+
filtered_count = original_count - len(all_skills)
|
|
1060
|
+
self.logger.info(
|
|
1061
|
+
f"Selective deployment: {len(all_skills)} of {original_count} skills "
|
|
1062
|
+
f"match agent requirements ({filtered_count} filtered out)"
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1036
1065
|
self.logger.info(
|
|
1037
1066
|
f"Deploying {len(all_skills)} skills to {target_dir} (force={force})"
|
|
1038
1067
|
)
|
|
@@ -1083,6 +1112,7 @@ class GitSkillSourceManager:
|
|
|
1083
1112
|
"deployed_skills": deployed,
|
|
1084
1113
|
"skipped_skills": skipped,
|
|
1085
1114
|
"errors": errors,
|
|
1115
|
+
"filtered_count": filtered_count,
|
|
1086
1116
|
}
|
|
1087
1117
|
|
|
1088
1118
|
def _deploy_single_skill(
|