claude-mpm 5.1.9__py3-none-any.whl → 5.4.22__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 (176) 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 +290 -34
  5. claude_mpm/agents/agent_loader.py +13 -44
  6. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  7. claude_mpm/cli/__main__.py +4 -0
  8. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  9. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  10. claude_mpm/cli/commands/agents.py +0 -31
  11. claude_mpm/cli/commands/auto_configure.py +210 -25
  12. claude_mpm/cli/commands/config.py +88 -2
  13. claude_mpm/cli/commands/configure.py +1097 -158
  14. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  15. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  16. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  17. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  18. claude_mpm/cli/commands/skills.py +214 -189
  19. claude_mpm/cli/commands/summarize.py +413 -0
  20. claude_mpm/cli/executor.py +11 -3
  21. claude_mpm/cli/parsers/agents_parser.py +0 -9
  22. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  23. claude_mpm/cli/parsers/base_parser.py +5 -0
  24. claude_mpm/cli/parsers/config_parser.py +153 -83
  25. claude_mpm/cli/parsers/skills_parser.py +3 -2
  26. claude_mpm/cli/startup.py +550 -94
  27. claude_mpm/commands/mpm-config.md +265 -0
  28. claude_mpm/commands/mpm-help.md +14 -95
  29. claude_mpm/commands/mpm-organize.md +500 -0
  30. claude_mpm/config/agent_sources.py +27 -0
  31. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  32. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  33. claude_mpm/core/framework_loader.py +4 -2
  34. claude_mpm/core/logger.py +13 -0
  35. claude_mpm/core/socketio_pool.py +3 -3
  36. claude_mpm/core/unified_agent_registry.py +5 -15
  37. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  38. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  39. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  40. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  41. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  42. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  43. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  44. claude_mpm/hooks/memory_integration_hook.py +46 -1
  45. claude_mpm/init.py +0 -19
  46. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  47. claude_mpm/scripts/launch_monitor.py +93 -13
  48. claude_mpm/scripts/start_activity_logging.py +0 -0
  49. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  50. claude_mpm/services/agents/agent_review_service.py +280 -0
  51. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  52. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  53. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  54. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
  55. claude_mpm/services/agents/git_source_manager.py +34 -0
  56. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  57. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  58. claude_mpm/services/agents/toolchain_detector.py +10 -6
  59. claude_mpm/services/analysis/__init__.py +11 -1
  60. claude_mpm/services/analysis/clone_detector.py +1030 -0
  61. claude_mpm/services/command_deployment_service.py +81 -10
  62. claude_mpm/services/event_bus/config.py +3 -1
  63. claude_mpm/services/git/git_operations_service.py +93 -8
  64. claude_mpm/services/monitor/daemon.py +9 -2
  65. claude_mpm/services/monitor/daemon_manager.py +39 -3
  66. claude_mpm/services/monitor/server.py +225 -19
  67. claude_mpm/services/self_upgrade_service.py +120 -12
  68. claude_mpm/services/skills/__init__.py +3 -0
  69. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  70. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  71. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  72. claude_mpm/services/skills_deployer.py +126 -9
  73. claude_mpm/services/socketio/event_normalizer.py +15 -1
  74. claude_mpm/services/socketio/server/core.py +160 -21
  75. claude_mpm/services/version_control/git_operations.py +103 -0
  76. claude_mpm/utils/agent_filters.py +17 -44
  77. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
  78. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +82 -161
  79. claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
  80. claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
  81. claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
  82. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  83. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  84. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  85. claude_mpm/agents/BASE_OPS.md +0 -219
  86. claude_mpm/agents/BASE_PM.md +0 -480
  87. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  88. claude_mpm/agents/BASE_QA.md +0 -167
  89. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  90. claude_mpm/agents/base_agent.json +0 -31
  91. claude_mpm/agents/base_agent_loader.py +0 -601
  92. claude_mpm/cli/commands/agents_detect.py +0 -380
  93. claude_mpm/cli/commands/agents_recommend.py +0 -309
  94. claude_mpm/cli/ticket_cli.py +0 -35
  95. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  96. claude_mpm/commands/mpm-agents-detect.md +0 -177
  97. claude_mpm/commands/mpm-agents-list.md +0 -131
  98. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  99. claude_mpm/commands/mpm-config-view.md +0 -150
  100. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  101. claude_mpm/dashboard/analysis_runner.py +0 -455
  102. claude_mpm/dashboard/index.html +0 -13
  103. claude_mpm/dashboard/open_dashboard.py +0 -66
  104. claude_mpm/dashboard/static/css/activity.css +0 -1958
  105. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  106. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  107. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  108. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  109. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  110. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  111. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  112. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  113. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  114. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  115. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  116. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  117. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  118. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  119. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  120. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  121. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  122. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  123. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  124. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  125. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  126. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  127. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  128. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  129. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  130. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  131. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  132. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  133. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  134. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  135. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  136. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  137. claude_mpm/dashboard/templates/code_simple.html +0 -153
  138. claude_mpm/dashboard/templates/index.html +0 -606
  139. claude_mpm/dashboard/test_dashboard.html +0 -372
  140. claude_mpm/scripts/mcp_server.py +0 -75
  141. claude_mpm/scripts/mcp_wrapper.py +0 -39
  142. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  143. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  144. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  145. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  146. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  147. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  148. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  149. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  150. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  151. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  152. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  153. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  154. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  155. claude_mpm/services/mcp_gateway/main.py +0 -589
  156. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  157. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  158. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  159. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  160. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  161. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  162. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  163. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  164. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  165. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  166. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  167. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  168. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  169. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  170. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  171. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  172. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  173. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  174. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  175. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
  176. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,278 @@
1
+ """Agent Recommendation Service
2
+
3
+ WHY: Provides intelligent agent recommendations based on toolchain detection
4
+ and always-recommended core agents. Helps users discover and install the
5
+ most relevant agents for their project without manual selection.
6
+
7
+ DESIGN DECISION: Uses toolchain analysis to map detected languages/frameworks
8
+ to specific engineer agents, plus always includes core agents.
9
+
10
+ Architecture:
11
+ - Toolchain-based recommendations: Python → python-engineer, etc.
12
+ - Core agents (always recommended): engineer, qa-agent, memory-manager-agent, local-ops-agent,
13
+ research-agent, documentation-agent, security-agent
14
+ - Confidence-based filtering: Only recommend high-confidence detections
15
+ """
16
+
17
+ from pathlib import Path
18
+ from typing import Dict, List, Optional, Set
19
+
20
+ from ...services.project.toolchain_analyzer import ToolchainAnalyzerService
21
+
22
+
23
+ class AgentRecommendationService:
24
+ """Service for recommending agents based on project toolchain.
25
+
26
+ WHY: Users shouldn't have to manually figure out which agents to install.
27
+ This service provides intelligent recommendations based on detected stack.
28
+
29
+ DESIGN DECISION: Separated from configure.py for reusability and testability.
30
+ Can be used by CLI, API, or future auto-configuration features.
31
+ """
32
+
33
+ # Core agents always included - matches ToolchainDetector.CORE_AGENTS
34
+ # Uses exact agent IDs from repository for consistency
35
+ CORE_AGENTS = {
36
+ "engineer",
37
+ "qa-agent",
38
+ "memory-manager-agent",
39
+ "local-ops-agent",
40
+ "research-agent",
41
+ "documentation-agent",
42
+ "security-agent",
43
+ }
44
+
45
+ # Map detected languages to recommended engineer agents
46
+ LANGUAGE_TO_AGENTS: Dict[str, List[str]] = {
47
+ "python": [
48
+ "engineer/backend/python-engineer",
49
+ "qa/api-qa",
50
+ ],
51
+ "javascript": [
52
+ "engineer/backend/javascript-engineer",
53
+ "engineer/data/typescript-engineer",
54
+ ],
55
+ "typescript": [
56
+ "engineer/data/typescript-engineer",
57
+ "engineer/backend/javascript-engineer",
58
+ ],
59
+ "rust": [
60
+ "engineer/backend/rust-engineer",
61
+ ],
62
+ "go": [
63
+ "engineer/backend/golang-engineer",
64
+ ],
65
+ "java": [
66
+ "engineer/backend/java-engineer",
67
+ ],
68
+ "dart": [
69
+ "engineer/mobile/dart-engineer",
70
+ ],
71
+ "php": [
72
+ "engineer/backend/php-engineer",
73
+ ],
74
+ "ruby": [
75
+ "engineer/backend/ruby-engineer",
76
+ ],
77
+ "swift": [
78
+ "engineer/mobile/swift-engineer",
79
+ ],
80
+ "kotlin": [
81
+ "engineer/mobile/kotlin-engineer",
82
+ ],
83
+ }
84
+
85
+ # Map detected frameworks to recommended agents
86
+ FRAMEWORK_TO_AGENTS: Dict[str, List[str]] = {
87
+ # Frontend frameworks
88
+ "react": [
89
+ "engineer/frontend/react-engineer",
90
+ "qa/web-qa",
91
+ ],
92
+ "nextjs": [
93
+ "engineer/frontend/nextjs-engineer",
94
+ "engineer/frontend/react-engineer",
95
+ "ops/platform/vercel-ops",
96
+ ],
97
+ "vue": [
98
+ "engineer/frontend/vue-engineer",
99
+ ],
100
+ "angular": [
101
+ "engineer/frontend/angular-engineer",
102
+ ],
103
+ # Backend frameworks
104
+ "fastapi": [
105
+ "engineer/backend/python-engineer",
106
+ "qa/api-qa",
107
+ ],
108
+ "django": [
109
+ "engineer/backend/python-engineer",
110
+ "qa/api-qa",
111
+ ],
112
+ "flask": [
113
+ "engineer/backend/python-engineer",
114
+ "qa/api-qa",
115
+ ],
116
+ "express": [
117
+ "engineer/backend/javascript-engineer",
118
+ "qa/api-qa",
119
+ ],
120
+ "nest": [
121
+ "engineer/backend/javascript-engineer",
122
+ "qa/api-qa",
123
+ ],
124
+ # Mobile frameworks
125
+ "flutter": [
126
+ "engineer/mobile/dart-engineer",
127
+ ],
128
+ "react-native": [
129
+ "engineer/frontend/react-engineer",
130
+ "engineer/mobile/react-native-engineer",
131
+ ],
132
+ # Desktop frameworks
133
+ "tauri": [
134
+ "engineer/mobile/tauri-engineer",
135
+ "engineer/backend/rust-engineer",
136
+ ],
137
+ "electron": [
138
+ "engineer/backend/javascript-engineer",
139
+ ],
140
+ }
141
+
142
+ def __init__(self, toolchain_analyzer: Optional[ToolchainAnalyzerService] = None):
143
+ """Initialize agent recommendation service.
144
+
145
+ Args:
146
+ toolchain_analyzer: Optional pre-initialized toolchain analyzer.
147
+ If None, creates a new instance.
148
+ """
149
+ self.toolchain_analyzer = toolchain_analyzer or ToolchainAnalyzerService()
150
+
151
+ def get_recommended_agents(
152
+ self,
153
+ project_path: Optional[str] = None,
154
+ confidence_threshold: float = 0.5,
155
+ ) -> Set[str]:
156
+ """Get recommended agents for a project.
157
+
158
+ Args:
159
+ project_path: Path to project directory. Defaults to cwd.
160
+ confidence_threshold: Minimum confidence for recommendations (0.0-1.0).
161
+ Only include detected components above this threshold.
162
+
163
+ Returns:
164
+ Set of recommended agent IDs (e.g., {"qa-agent", "research-agent", ...})
165
+
166
+ Example:
167
+ >>> service = AgentRecommendationService()
168
+ >>> recommended = service.get_recommended_agents()
169
+ >>> "qa-agent" in recommended
170
+ True
171
+ >>> # For Python project:
172
+ >>> "engineer/backend/python-engineer" in recommended
173
+ True
174
+ """
175
+ # Start with core agents (always recommended)
176
+ recommended = self.CORE_AGENTS.copy()
177
+
178
+ # Analyze project toolchain
179
+ if project_path is None:
180
+ project_path = str(Path.cwd())
181
+
182
+ try:
183
+ # ToolchainAnalyzerService.analyze_toolchain takes Path, not str
184
+ analysis = self.toolchain_analyzer.analyze_toolchain(Path(project_path))
185
+ except Exception as e:
186
+ # If analysis fails, just return core agents
187
+ print(f"Warning: Toolchain analysis failed: {e}")
188
+ return recommended
189
+
190
+ # Add language-specific agents
191
+ # ToolchainAnalysis has a single LanguageDetection object, not a list
192
+ if analysis.language_detection:
193
+ # Check primary language
194
+ primary_lang = analysis.language_detection.primary_language.lower()
195
+ if primary_lang in self.LANGUAGE_TO_AGENTS:
196
+ recommended.update(self.LANGUAGE_TO_AGENTS[primary_lang])
197
+
198
+ # Check secondary languages
199
+ for lang_component in analysis.language_detection.secondary_languages:
200
+ lang = lang_component.name.lower()
201
+ if lang in self.LANGUAGE_TO_AGENTS:
202
+ recommended.update(self.LANGUAGE_TO_AGENTS[lang])
203
+
204
+ # Add framework-specific agents
205
+ for framework in analysis.frameworks:
206
+ fw_name = framework.name.lower()
207
+ if fw_name in self.FRAMEWORK_TO_AGENTS:
208
+ recommended.update(self.FRAMEWORK_TO_AGENTS[fw_name])
209
+
210
+ return recommended
211
+
212
+ def get_detection_summary(self, project_path: Optional[str] = None) -> Dict:
213
+ """Get human-readable summary of detected toolchain.
214
+
215
+ Args:
216
+ project_path: Path to project directory. Defaults to cwd.
217
+
218
+ Returns:
219
+ Dict with keys:
220
+ - detected_languages: List of detected language names
221
+ - detected_frameworks: List of detected framework names
222
+ - recommended_count: Number of recommended agents
223
+ - detection_quality: "high", "medium", "low", or "none"
224
+
225
+ Example:
226
+ >>> summary = service.get_detection_summary()
227
+ >>> summary['detected_languages']
228
+ ['Python', 'JavaScript']
229
+ >>> summary['recommended_count']
230
+ 15
231
+ """
232
+ if project_path is None:
233
+ project_path = str(Path.cwd())
234
+
235
+ try:
236
+ analysis = self.toolchain_analyzer.analyze_toolchain(Path(project_path))
237
+
238
+ # Extract languages from LanguageDetection object
239
+ languages = []
240
+ if analysis.language_detection:
241
+ languages.append(analysis.language_detection.primary_language)
242
+ languages.extend(
243
+ comp.name
244
+ for comp in analysis.language_detection.secondary_languages
245
+ )
246
+
247
+ # Extract frameworks
248
+ frameworks = [fw.name for fw in analysis.frameworks]
249
+
250
+ # Get recommended agents
251
+ recommended = self.get_recommended_agents(project_path)
252
+
253
+ # Determine detection quality from overall_confidence
254
+ confidence_map = {
255
+ "high": "high",
256
+ "medium": "medium",
257
+ "low": "low",
258
+ "very_low": "low",
259
+ }
260
+ quality = confidence_map.get(
261
+ str(analysis.overall_confidence).lower(), "unknown"
262
+ )
263
+
264
+ return {
265
+ "detected_languages": languages,
266
+ "detected_frameworks": frameworks,
267
+ "recommended_count": len(recommended),
268
+ "detection_quality": quality,
269
+ }
270
+ except Exception as e:
271
+ # Log the error for debugging
272
+ print(f"Warning: Toolchain analysis failed: {e}")
273
+ return {
274
+ "detected_languages": [],
275
+ "detected_frameworks": [],
276
+ "recommended_count": len(self.CORE_AGENTS),
277
+ "detection_quality": "none",
278
+ }
@@ -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)
@@ -129,12 +129,13 @@ class AgentTemplateBuilder:
129
129
  base_templates.append(base_agent_file)
130
130
  self.logger.debug(f"Found BASE-AGENT.md at: {base_agent_file}")
131
131
 
132
- # Stop at git repository root if detected
132
+ # Stop at git repository root if detected (check AFTER finding BASE-AGENT.md)
133
133
  if (current_dir / ".git").exists():
134
134
  self.logger.debug(f"Reached git repository root at: {current_dir}")
135
135
  break
136
136
 
137
- # Stop at common repository root indicators
137
+ # Stop at common repository root indicators (check AFTER finding BASE-AGENT.md)
138
+ # This ensures we check the 'agents' directory before stopping at 'remote-agents'
138
139
  if current_dir.name in [".claude-mpm", "remote-agents", "cache"]:
139
140
  self.logger.debug(
140
141
  f"Reached repository root indicator at: {current_dir}"
@@ -675,6 +676,7 @@ Only include memories that are:
675
676
  """
676
677
  content = content + memory_instructions
677
678
 
679
+ # Combine frontmatter and content
678
680
  return frontmatter + content
679
681
 
680
682
  def build_agent_yaml(