claude-mpm 5.4.3__py3-none-any.whl → 5.4.21__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.

Files changed (90) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +166 -21
  5. claude_mpm/agents/agent_loader.py +3 -27
  6. claude_mpm/cli/__main__.py +4 -0
  7. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  8. claude_mpm/cli/commands/agents.py +0 -31
  9. claude_mpm/cli/commands/auto_configure.py +210 -25
  10. claude_mpm/cli/commands/config.py +88 -2
  11. claude_mpm/cli/commands/configure.py +85 -43
  12. claude_mpm/cli/commands/configure_agent_display.py +3 -1
  13. claude_mpm/cli/commands/mpm_init/core.py +2 -45
  14. claude_mpm/cli/commands/skills.py +214 -189
  15. claude_mpm/cli/executor.py +3 -3
  16. claude_mpm/cli/parsers/agents_parser.py +0 -9
  17. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  18. claude_mpm/cli/parsers/config_parser.py +153 -83
  19. claude_mpm/cli/parsers/skills_parser.py +3 -2
  20. claude_mpm/cli/startup.py +490 -41
  21. claude_mpm/commands/mpm-config.md +265 -0
  22. claude_mpm/commands/mpm-help.md +14 -95
  23. claude_mpm/commands/mpm-organize.md +350 -153
  24. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  25. claude_mpm/core/framework_loader.py +4 -2
  26. claude_mpm/core/logger.py +13 -0
  27. claude_mpm/hooks/claude_hooks/event_handlers.py +176 -76
  28. claude_mpm/hooks/claude_hooks/hook_handler.py +2 -0
  29. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  30. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  31. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  32. claude_mpm/hooks/memory_integration_hook.py +46 -1
  33. claude_mpm/init.py +0 -19
  34. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  35. claude_mpm/scripts/start_activity_logging.py +0 -0
  36. claude_mpm/services/agents/agent_recommendation_service.py +6 -7
  37. claude_mpm/services/agents/agent_review_service.py +280 -0
  38. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  39. claude_mpm/services/agents/deployment/agent_template_builder.py +1 -0
  40. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  41. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +13 -0
  42. claude_mpm/services/agents/git_source_manager.py +14 -0
  43. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  44. claude_mpm/services/agents/toolchain_detector.py +6 -3
  45. claude_mpm/services/command_deployment_service.py +81 -8
  46. claude_mpm/services/git/git_operations_service.py +93 -8
  47. claude_mpm/services/self_upgrade_service.py +120 -12
  48. claude_mpm/services/skills/__init__.py +3 -0
  49. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  50. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  51. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  52. claude_mpm/services/skills_deployer.py +126 -9
  53. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/METADATA +47 -8
  54. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/RECORD +58 -82
  55. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/entry_points.txt +0 -3
  56. claude_mpm-5.4.21.dist-info/licenses/LICENSE +94 -0
  57. claude_mpm-5.4.21.dist-info/licenses/LICENSE-FAQ.md +153 -0
  58. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  59. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  60. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  61. claude_mpm/agents/BASE_OPS.md +0 -219
  62. claude_mpm/agents/BASE_PM.md +0 -480
  63. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  64. claude_mpm/agents/BASE_QA.md +0 -167
  65. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  66. claude_mpm/agents/base_agent.json +0 -31
  67. claude_mpm/agents/base_agent_loader.py +0 -601
  68. claude_mpm/cli/commands/agents_detect.py +0 -380
  69. claude_mpm/cli/commands/agents_recommend.py +0 -309
  70. claude_mpm/cli/ticket_cli.py +0 -35
  71. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  72. claude_mpm/commands/mpm-agents-detect.md +0 -177
  73. claude_mpm/commands/mpm-agents-list.md +0 -131
  74. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  75. claude_mpm/commands/mpm-config-view.md +0 -150
  76. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  79. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  80. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  81. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  82. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  83. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  84. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  85. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  86. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  87. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  88. claude_mpm-5.4.3.dist-info/licenses/LICENSE +0 -21
  89. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/WHEEL +0 -0
  90. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/top_level.txt +0 -0
claude_mpm/init.py CHANGED
@@ -141,25 +141,6 @@ 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
-
163
144
  # Log successful creation with details
164
145
  self.logger.info(f"Initialized project directory at {self.project_dir}")
165
146
  self.logger.debug("Created directories: agents, config, responses, logs")
@@ -88,18 +88,21 @@ fi
88
88
  #
89
89
  # STRATEGY:
90
90
  # This function implements a fallback chain to find Python with claude-mpm dependencies:
91
- # 1. Project-specific virtual environments (venv, .venv)
92
- # 2. Currently active virtual environment ($VIRTUAL_ENV)
93
- # 3. System python3 (may lack dependencies)
94
- # 4. System python (last resort)
91
+ # 1. UV-managed projects (uv.lock detected) - uses "uv run python"
92
+ # 2. pipx installations - uses pipx venv Python
93
+ # 3. Project-specific virtual environments (venv, .venv)
94
+ # 4. Currently active virtual environment ($VIRTUAL_ENV)
95
+ # 5. System python3 (may lack dependencies)
96
+ # 6. System python (last resort)
95
97
  #
96
98
  # WHY THIS APPROACH:
97
99
  # - Claude MPM requires specific packages (socketio, eventlet) not in system Python
98
- # - Virtual environments ensure dependency isolation and availability
100
+ # - UV and virtual environments ensure dependency isolation and availability
99
101
  # - Multiple naming conventions supported (venv vs .venv)
100
102
  # - Graceful degradation to system Python if no venv found
101
103
  #
102
104
  # ACTIVATION STRATEGY:
105
+ # - UV projects: use "uv run python" to execute in UV-managed environment
103
106
  # - Sources activate script to set up environment variables
104
107
  # - Returns specific Python path for exec (not just 'python')
105
108
  # - Maintains environment in same shell process
@@ -110,10 +113,18 @@ fi
110
113
  # - Caches result in process environment
111
114
  #
112
115
  # RETURNS:
113
- # Absolute path to Python executable with claude-mpm dependencies
116
+ # Absolute path to Python executable with claude-mpm dependencies, or "uv run python" for UV projects
114
117
  #
115
118
  find_python_command() {
116
- # 1. Check if we're in a pipx installation first
119
+ # 1. Check for UV project first (uv.lock or pyproject.toml with uv)
120
+ if [ -f "$CLAUDE_MPM_ROOT/uv.lock" ]; then
121
+ if command -v uv &> /dev/null; then
122
+ echo "uv run python"
123
+ return
124
+ fi
125
+ fi
126
+
127
+ # 2. Check if we're in a pipx installation
117
128
  if [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
118
129
  # pipx installation - use the pipx venv's Python directly
119
130
  if [ -f "$CLAUDE_MPM_ROOT/bin/python" ]; then
@@ -122,7 +133,7 @@ find_python_command() {
122
133
  fi
123
134
  fi
124
135
 
125
- # 2. Check for project-local virtual environment (common in development)
136
+ # 3. Check for project-local virtual environment (common in development)
126
137
  if [ -f "$CLAUDE_MPM_ROOT/venv/bin/activate" ]; then
127
138
  source "$CLAUDE_MPM_ROOT/venv/bin/activate"
128
139
  echo "$CLAUDE_MPM_ROOT/venv/bin/python"
@@ -173,15 +184,44 @@ fi
173
184
  # Set Socket.IO configuration for hook events
174
185
  export CLAUDE_MPM_SOCKETIO_PORT="${CLAUDE_MPM_SOCKETIO_PORT:-8765}"
175
186
 
176
- # Run the Python hook handler with all input
177
- # Use exec to replace the shell process with Python
178
- if ! exec "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log; then
179
- # If the Python handler fails, always return continue to not block Claude
187
+ # Function for debug logging
188
+ log_debug() {
180
189
  if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
181
- echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/claude-mpm-hook-error.log" >> /tmp/claude-mpm-hook.log
182
- echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Error: $(cat /tmp/claude-mpm-hook-error.log 2>/dev/null | head -5)" >> /tmp/claude-mpm-hook.log
190
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] $1" >> /tmp/claude-mpm-hook.log
191
+ fi
192
+ }
193
+
194
+ # Test Python works and module exists
195
+ # Handle UV's multi-word command specially
196
+ if [[ "$PYTHON_CMD" == "uv run python" ]]; then
197
+ if ! uv run python -c "import claude_mpm" 2>/dev/null; then
198
+ log_debug "claude_mpm module not available, continuing without hook"
199
+ echo '{"action": "continue"}'
200
+ exit 0
201
+ fi
202
+ else
203
+ if ! $PYTHON_CMD -c "import claude_mpm" 2>/dev/null; then
204
+ log_debug "claude_mpm module not available, continuing without hook"
205
+ echo '{"action": "continue"}'
206
+ exit 0
183
207
  fi
184
- # Return continue action to prevent blocking Claude Code
185
- echo '{"action": "continue"}'
186
- exit 0
187
- fi
208
+ fi
209
+
210
+ # Run the Python hook handler with all input
211
+ # Use exec to replace the shell process with Python
212
+ # Handle UV's multi-word command specially
213
+ if [[ "$PYTHON_CMD" == "uv run python" ]]; then
214
+ exec uv run python -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log
215
+ else
216
+ exec "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log
217
+ fi
218
+
219
+ # Note: exec replaces the shell process, so code below only runs if exec fails
220
+ # If we reach here, the Python handler failed
221
+ if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
222
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/claude-mpm-hook-error.log" >> /tmp/claude-mpm-hook.log
223
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Error: $(cat /tmp/claude-mpm-hook-error.log 2>/dev/null | head -5)" >> /tmp/claude-mpm-hook.log
224
+ fi
225
+ # Return continue action to prevent blocking Claude Code
226
+ echo '{"action": "continue"}'
227
+ exit 0
File without changes
@@ -9,8 +9,8 @@ to specific engineer agents, plus always includes core agents.
9
9
 
10
10
  Architecture:
11
11
  - Toolchain-based recommendations: Python → python-engineer, etc.
12
- - Core agents (always recommended): qa-agent, research-agent, documentation-agent,
13
- ticketing, local-ops-agent, version-control, security
12
+ - Core agents (always recommended): engineer, qa-agent, memory-manager-agent, local-ops-agent,
13
+ research-agent, documentation-agent, security-agent
14
14
  - Confidence-based filtering: Only recommend high-confidence detections
15
15
  """
16
16
 
@@ -33,14 +33,13 @@ class AgentRecommendationService:
33
33
  # Core agents always included - matches ToolchainDetector.CORE_AGENTS
34
34
  # Uses exact agent IDs from repository for consistency
35
35
  CORE_AGENTS = {
36
+ "engineer",
36
37
  "qa-agent",
38
+ "memory-manager-agent",
39
+ "local-ops-agent",
37
40
  "research-agent",
38
41
  "documentation-agent",
39
- "ticketing",
40
- "local-ops-agent",
41
- # Keep version-control and security as universal recommended agents
42
- "version-control",
43
- "security",
42
+ "security-agent",
44
43
  }
45
44
 
46
45
  # Map detected languages to recommended engineer agents
@@ -0,0 +1,280 @@
1
+ """Agent review service for comparing project agents with managed agents.
2
+
3
+ WHY: This service helps users maintain a clean agent directory by:
4
+ 1. Identifying which agents are managed vs custom
5
+ 2. Detecting outdated versions of managed agents
6
+ 3. Finding unused agents that don't match the detected toolchain
7
+ 4. Safely archiving unnecessary agents instead of deleting them
8
+
9
+ DESIGN DECISIONS:
10
+ - Archive to .claude/agents/unused/ instead of deleting (safe, recoverable)
11
+ - Add timestamps to archived files to prevent conflicts
12
+ - Preserve custom user agents (not in managed set)
13
+ - Compare versions to detect outdated managed agents
14
+ """
15
+
16
+ import shutil
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Set
20
+
21
+ from claude_mpm.core.logging_config import get_logger
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class AgentReviewService:
27
+ """Service for reviewing and managing project agents.
28
+
29
+ This service analyzes the relationship between project agents and managed
30
+ agents from the claude-mpm-agents repository, categorizing them as:
31
+ - Managed: In sync with managed agents
32
+ - Outdated: Older version of managed agent exists
33
+ - Custom: User-created agents not in managed set
34
+ - Unused: Not recommended for this project's toolchain
35
+ """
36
+
37
+ def __init__(self):
38
+ """Initialize the agent review service."""
39
+ self.logger = get_logger(__name__)
40
+
41
+ def review_project_agents(
42
+ self,
43
+ project_agents_dir: Path,
44
+ managed_agents: List[Dict[str, Any]],
45
+ recommended_agent_ids: Set[str],
46
+ ) -> Dict[str, List[Dict[str, Any]]]:
47
+ """Review existing project agents and categorize them.
48
+
49
+ Args:
50
+ project_agents_dir: Directory containing project agents (.claude/agents/)
51
+ managed_agents: List of managed agent dicts from cache
52
+ recommended_agent_ids: Set of agent IDs recommended for this toolchain
53
+
54
+ Returns:
55
+ Dictionary with categorized agents:
56
+ {
57
+ "managed": [...], # In sync with managed
58
+ "outdated": [...], # Older version exists
59
+ "custom": [...], # User-created
60
+ "unused": [...], # Not needed for this toolchain
61
+ }
62
+ """
63
+ results = {
64
+ "managed": [],
65
+ "outdated": [],
66
+ "custom": [],
67
+ "unused": [],
68
+ }
69
+
70
+ if not project_agents_dir.exists():
71
+ self.logger.debug(
72
+ f"Project agents directory does not exist: {project_agents_dir}"
73
+ )
74
+ return results
75
+
76
+ # Build lookup map of managed agents by ID
77
+ managed_by_id = {agent["agent_id"]: agent for agent in managed_agents}
78
+
79
+ # Scan project agents
80
+ for agent_file in project_agents_dir.glob("*.md"):
81
+ # Skip the unused directory itself
82
+ if agent_file.name == "unused":
83
+ continue
84
+
85
+ agent_name = agent_file.stem
86
+
87
+ # Parse agent to get version and metadata
88
+ project_agent_info = self._parse_project_agent(agent_file)
89
+
90
+ # Check if this is a managed agent
91
+ if agent_name in managed_by_id:
92
+ managed_agent = managed_by_id[agent_name]
93
+
94
+ # Compare versions
95
+ project_version = project_agent_info.get("version", "unknown")
96
+ managed_version = managed_agent.get("version", "unknown")
97
+
98
+ if self._is_outdated(project_version, managed_version):
99
+ # Outdated version of managed agent
100
+ results["outdated"].append(
101
+ {
102
+ "name": agent_name,
103
+ "path": agent_file,
104
+ "current_version": project_version,
105
+ "available_version": managed_version,
106
+ "recommended": agent_name in recommended_agent_ids,
107
+ }
108
+ )
109
+ else:
110
+ # Up-to-date managed agent
111
+ results["managed"].append(
112
+ {
113
+ "name": agent_name,
114
+ "path": agent_file,
115
+ "version": project_version,
116
+ "recommended": agent_name in recommended_agent_ids,
117
+ }
118
+ )
119
+ else:
120
+ # Custom user agent (not in managed set)
121
+ results["custom"].append(
122
+ {
123
+ "name": agent_name,
124
+ "path": agent_file,
125
+ "version": project_agent_info.get("version", "unknown"),
126
+ }
127
+ )
128
+
129
+ # Identify unused agents (managed or outdated but not recommended)
130
+ for category in ["managed", "outdated"]:
131
+ for agent in results[category][:]: # Copy list to modify during iteration
132
+ if not agent.get("recommended", False):
133
+ # This managed/outdated agent is not recommended for this toolchain
134
+ results["unused"].append(agent)
135
+ results[category].remove(agent)
136
+
137
+ self.logger.info(
138
+ f"Agent review complete: "
139
+ f"{len(results['managed'])} managed, "
140
+ f"{len(results['outdated'])} outdated, "
141
+ f"{len(results['custom'])} custom, "
142
+ f"{len(results['unused'])} unused"
143
+ )
144
+
145
+ return results
146
+
147
+ def archive_agents(
148
+ self, agents_to_archive: List[Dict[str, Any]], project_agents_dir: Path
149
+ ) -> Dict[str, Any]:
150
+ """Archive agents by moving them to .claude/agents/unused/.
151
+
152
+ Args:
153
+ agents_to_archive: List of agent dicts with 'name' and 'path' keys
154
+ project_agents_dir: Base agents directory (.claude/agents/)
155
+
156
+ Returns:
157
+ Dictionary with archival results:
158
+ {
159
+ "archived": [...], # Successfully archived
160
+ "errors": [...], # Archival errors
161
+ }
162
+ """
163
+ results = {"archived": [], "errors": []}
164
+
165
+ if not agents_to_archive:
166
+ return results
167
+
168
+ # Create unused directory
169
+ unused_dir = project_agents_dir / "unused"
170
+ unused_dir.mkdir(exist_ok=True)
171
+
172
+ for agent in agents_to_archive:
173
+ agent_path = agent["path"]
174
+ agent_name = agent["name"]
175
+
176
+ try:
177
+ # Generate timestamped filename to avoid conflicts
178
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
179
+ archived_name = f"{agent_name}_{timestamp}.md"
180
+ archived_path = unused_dir / archived_name
181
+
182
+ # Move the file
183
+ shutil.move(str(agent_path), str(archived_path))
184
+
185
+ results["archived"].append(
186
+ {
187
+ "name": agent_name,
188
+ "original_path": str(agent_path),
189
+ "archived_path": str(archived_path),
190
+ }
191
+ )
192
+
193
+ self.logger.debug(f"Archived {agent_name} to {archived_path}")
194
+
195
+ except Exception as e:
196
+ error_msg = f"Failed to archive {agent_name}: {e}"
197
+ self.logger.error(error_msg)
198
+ results["errors"].append(error_msg)
199
+
200
+ self.logger.info(
201
+ f"Archived {len(results['archived'])} agents, "
202
+ f"{len(results['errors'])} errors"
203
+ )
204
+
205
+ return results
206
+
207
+ def _parse_project_agent(self, agent_file: Path) -> Dict[str, Any]:
208
+ """Parse a project agent file to extract metadata.
209
+
210
+ Args:
211
+ agent_file: Path to agent Markdown file
212
+
213
+ Returns:
214
+ Dictionary with agent metadata (version, name, etc.)
215
+ """
216
+ try:
217
+ content = agent_file.read_text(encoding="utf-8")
218
+
219
+ # Extract version from YAML frontmatter
220
+ import re
221
+
222
+ version_match = re.search(
223
+ r'^version:\s*["\']?(.+?)["\']?$', content, re.MULTILINE
224
+ )
225
+ version = version_match.group(1) if version_match else "unknown"
226
+
227
+ return {
228
+ "version": version,
229
+ "name": agent_file.stem,
230
+ }
231
+
232
+ except Exception as e:
233
+ self.logger.warning(f"Failed to parse agent {agent_file.name}: {e}")
234
+ return {"version": "unknown", "name": agent_file.stem}
235
+
236
+ def _is_outdated(self, current_version: str, available_version: str) -> bool:
237
+ """Check if current version is outdated compared to available version.
238
+
239
+ Args:
240
+ current_version: Currently deployed version
241
+ available_version: Available version from managed agents
242
+
243
+ Returns:
244
+ True if current version is outdated
245
+ """
246
+ # Handle unknown versions
247
+ if current_version == "unknown" or available_version == "unknown":
248
+ return False
249
+
250
+ # Simple string comparison for now
251
+ # TODO: Implement semantic version comparison (1.2.3 vs 1.2.4)
252
+ return current_version != available_version
253
+
254
+ def get_archive_summary(self, project_agents_dir: Path) -> Dict[str, Any]:
255
+ """Get summary of archived agents.
256
+
257
+ Args:
258
+ project_agents_dir: Base agents directory (.claude/agents/)
259
+
260
+ Returns:
261
+ Dictionary with archive statistics
262
+ """
263
+ unused_dir = project_agents_dir / "unused"
264
+
265
+ if not unused_dir.exists():
266
+ return {"count": 0, "agents": []}
267
+
268
+ archived_files = list(unused_dir.glob("*.md"))
269
+
270
+ return {
271
+ "count": len(archived_files),
272
+ "agents": [
273
+ {
274
+ "name": f.stem,
275
+ "path": str(f),
276
+ "size_bytes": f.stat().st_size,
277
+ }
278
+ for f in archived_files
279
+ ],
280
+ }
@@ -215,9 +215,8 @@ class AgentDiscoveryService:
215
215
  # Extract YAML frontmatter
216
216
  frontmatter = self._extract_yaml_frontmatter(template_content)
217
217
  if not frontmatter:
218
- self.logger.warning(
219
- f"No valid YAML frontmatter in {template_file.name}"
220
- )
218
+ # Silently return None for files without frontmatter
219
+ # (e.g., PM instruction templates in templates/ directory)
221
220
  return None
222
221
 
223
222
  # Extract metadata directly from frontmatter (flat structure)
@@ -676,6 +676,7 @@ Only include memories that are:
676
676
  """
677
677
  content = content + memory_instructions
678
678
 
679
+ # Combine frontmatter and content
679
680
  return frontmatter + content
680
681
 
681
682
  def build_agent_yaml(
@@ -16,6 +16,8 @@ import os
16
16
  from pathlib import Path
17
17
  from typing import Any, Dict, List, Optional, Tuple
18
18
 
19
+ import yaml
20
+
19
21
  from claude_mpm.core.config import Config
20
22
  from claude_mpm.core.logging_config import get_logger
21
23
 
@@ -51,6 +53,70 @@ class MultiSourceAgentDeploymentService:
51
53
  self.logger = get_logger(__name__)
52
54
  self.version_manager = AgentVersionManager()
53
55
 
56
+ def _read_template_version(self, template_path: Path) -> Optional[str]:
57
+ """Read version from template file (supports both .md and .json formats).
58
+
59
+ For .md files: Extract version from YAML frontmatter
60
+ For .json files: Extract version from JSON structure
61
+
62
+ Args:
63
+ template_path: Path to template file
64
+
65
+ Returns:
66
+ Version string or None if version cannot be extracted
67
+ """
68
+ try:
69
+ if template_path.suffix == ".md":
70
+ # Parse markdown with YAML frontmatter
71
+ content = template_path.read_text()
72
+
73
+ # Extract YAML frontmatter (between --- markers)
74
+ if not content.strip().startswith("---"):
75
+ return None
76
+
77
+ parts = content.split("---", 2)
78
+ if len(parts) < 3:
79
+ return None
80
+
81
+ # Parse YAML frontmatter
82
+ frontmatter = yaml.safe_load(parts[1])
83
+ if not frontmatter:
84
+ return None
85
+
86
+ # Extract version from frontmatter
87
+ version = frontmatter.get("version")
88
+ return version if version else None
89
+
90
+ if template_path.suffix == ".json":
91
+ # Parse JSON template
92
+ template_data = json.loads(template_path.read_text())
93
+ metadata = template_data.get("metadata", {})
94
+ version = (
95
+ template_data.get("agent_version")
96
+ or template_data.get("version")
97
+ or metadata.get("version")
98
+ )
99
+ return version if version else None
100
+
101
+ self.logger.warning(
102
+ f"Unknown template format: {template_path.suffix} for {template_path.name}"
103
+ )
104
+ return None
105
+
106
+ except yaml.YAMLError as e:
107
+ self.logger.warning(
108
+ f"Invalid YAML frontmatter in {template_path.name}: {e}"
109
+ )
110
+ return None
111
+ except json.JSONDecodeError as e:
112
+ self.logger.warning(f"Invalid JSON in {template_path.name}: {e}")
113
+ return None
114
+ except Exception as e:
115
+ self.logger.warning(
116
+ f"Error reading template version from {template_path.name}: {e}"
117
+ )
118
+ return None
119
+
54
120
  def _build_canonical_id_for_agent(self, agent_info: Dict[str, Any]) -> str:
55
121
  """Build or retrieve canonical_id for an agent.
56
122
 
@@ -827,17 +893,20 @@ class MultiSourceAgentDeploymentService:
827
893
  comparison_results["needs_update"].append(agent_name)
828
894
  continue
829
895
 
830
- # Read template version
831
- try:
832
- template_data = json.loads(template_path.read_text())
833
- metadata = template_data.get("metadata", {})
834
- template_version = self.version_manager.parse_version(
835
- template_data.get("agent_version")
836
- or template_data.get("version")
837
- or metadata.get("version", "0.0.0")
896
+ # Read template version using format-aware helper
897
+ version_string = self._read_template_version(template_path)
898
+ if not version_string:
899
+ self.logger.warning(
900
+ f"Could not extract version from template for '{agent_name}', skipping"
838
901
  )
902
+ continue
903
+
904
+ try:
905
+ template_version = self.version_manager.parse_version(version_string)
839
906
  except Exception as e:
840
- self.logger.warning(f"Error reading template for '{agent_name}': {e}")
907
+ self.logger.warning(
908
+ f"Error parsing version '{version_string}' for '{agent_name}': {e}"
909
+ )
841
910
  continue
842
911
 
843
912
  # Read deployed version
@@ -464,9 +464,22 @@ class RemoteAgentDiscoveryService:
464
464
  "AUTO-DEPLOY-INDEX.md",
465
465
  "PHASE1_COMPLETE.md",
466
466
  "AGENTS.md",
467
+ # Skill-related files (should not be treated as agents)
468
+ "SKILL.md",
469
+ "SKILLS.md",
470
+ "skill-template.md",
467
471
  }
468
472
  md_files = [f for f in md_files if f.name not in excluded_files]
469
473
 
474
+ # Filter out files from skills-related directories
475
+ # Skills are not agents and should not be discovered here
476
+ excluded_directory_patterns = {"references", "examples", "claude-mpm-skills"}
477
+ md_files = [
478
+ f
479
+ for f in md_files
480
+ if not any(excluded in f.parts for excluded in excluded_directory_patterns)
481
+ ]
482
+
470
483
  # In flattened cache mode, also exclude files from git repository subdirectories
471
484
  # (files under directories that contain .git folder)
472
485
  if scan_dir == self.remote_agents_dir:
@@ -351,6 +351,12 @@ class GitSourceManager:
351
351
  "claude-mpm",
352
352
  }
353
353
 
354
+ # Repositories that are NOT agent repositories (should be excluded from agent discovery)
355
+ # These contain skills, documentation, or other non-agent content
356
+ EXCLUDED_REPOSITORIES = {
357
+ "claude-mpm-skills", # Skills repository, not agents
358
+ }
359
+
354
360
  for owner_dir in self.cache_root.iterdir():
355
361
  if not owner_dir.is_dir():
356
362
  continue
@@ -367,6 +373,14 @@ class GitSourceManager:
367
373
  for repo_dir in owner_dir.iterdir():
368
374
  if not repo_dir.is_dir():
369
375
  continue
376
+
377
+ # Skip excluded repositories (e.g., skills repos are not agent repos)
378
+ if repo_dir.name in EXCLUDED_REPOSITORIES:
379
+ logger.debug(
380
+ f"[DEBUG] Skipping excluded repository: {repo_dir.name}"
381
+ )
382
+ continue
383
+
370
384
  logger.debug(f"[DEBUG] Processing repo_dir: {repo_dir.name}")
371
385
 
372
386
  # Bug #5 fix: Don't iterate subdirectories - RemoteAgentDiscoveryService
@@ -13,9 +13,6 @@ from enum import Enum
13
13
  from pathlib import Path
14
14
  from typing import Any, Dict, List, Optional
15
15
 
16
- # Lazy import for base_agent_loader to reduce initialization overhead
17
- # base_agent_loader adds ~500ms to import time
18
- # from claude_mpm.agents.base_agent_loader import clear_base_agent_cache
19
16
  from claude_mpm.core.logging_utils import get_logger
20
17
  from claude_mpm.services.memory.cache.shared_prompt_cache import SharedPromptCache
21
18
  from claude_mpm.services.shared import ConfigServiceBase
@@ -23,13 +20,6 @@ from claude_mpm.services.shared import ConfigServiceBase
23
20
  logger = get_logger(__name__)
24
21
 
25
22
 
26
- def _get_clear_base_agent_cache():
27
- """Lazy loader for clear_base_agent_cache function."""
28
- from claude_mpm.agents.base_agent_loader import clear_base_agent_cache
29
-
30
- return clear_base_agent_cache
31
-
32
-
33
23
  class BaseAgentSection(str, Enum):
34
24
  """Base agent markdown sections."""
35
25
 
@@ -143,9 +133,7 @@ class BaseAgentManager(ConfigServiceBase):
143
133
  content = self._structure_to_markdown(current)
144
134
  self.base_agent_path.write_text(content, encoding="utf-8")
145
135
 
146
- # Clear caches (lazy load to avoid import overhead)
147
- clear_base_agent_cache = _get_clear_base_agent_cache()
148
- clear_base_agent_cache()
136
+ # Clear cache
149
137
  self.cache.invalidate("base_agent:instructions")
150
138
 
151
139
  logger.info("Base agent updated successfully")