claude-mpm 5.1.9__py3-none-any.whl → 5.4.48__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 (248) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/BASE_AGENT.md +164 -0
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
  5. claude_mpm/agents/MEMORY.md +1 -1
  6. claude_mpm/agents/PM_INSTRUCTIONS.md +843 -900
  7. claude_mpm/agents/WORKFLOW.md +5 -254
  8. claude_mpm/agents/agent_loader.py +13 -44
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/frontmatter_validator.py +2 -2
  11. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  12. claude_mpm/cli/__main__.py +4 -0
  13. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  14. claude_mpm/cli/commands/agent_state_manager.py +18 -27
  15. claude_mpm/cli/commands/agents.py +9 -40
  16. claude_mpm/cli/commands/auto_configure.py +210 -25
  17. claude_mpm/cli/commands/config.py +88 -2
  18. claude_mpm/cli/commands/configure.py +1098 -159
  19. claude_mpm/cli/commands/configure_agent_display.py +25 -6
  20. claude_mpm/cli/commands/mpm_init/core.py +225 -46
  21. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  22. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  23. claude_mpm/cli/commands/postmortem.py +1 -1
  24. claude_mpm/cli/commands/profile.py +277 -0
  25. claude_mpm/cli/commands/skills.py +218 -197
  26. claude_mpm/cli/commands/summarize.py +413 -0
  27. claude_mpm/cli/executor.py +21 -3
  28. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  29. claude_mpm/cli/parsers/agents_parser.py +0 -9
  30. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  31. claude_mpm/cli/parsers/base_parser.py +12 -0
  32. claude_mpm/cli/parsers/config_parser.py +153 -83
  33. claude_mpm/cli/parsers/profile_parser.py +148 -0
  34. claude_mpm/cli/parsers/skills_parser.py +0 -5
  35. claude_mpm/cli/startup.py +876 -149
  36. claude_mpm/commands/mpm-config.md +28 -0
  37. claude_mpm/commands/mpm-doctor.md +9 -22
  38. claude_mpm/commands/mpm-help.md +5 -287
  39. claude_mpm/commands/mpm-init.md +81 -507
  40. claude_mpm/commands/mpm-monitor.md +15 -402
  41. claude_mpm/commands/mpm-organize.md +120 -0
  42. claude_mpm/commands/mpm-postmortem.md +6 -108
  43. claude_mpm/commands/mpm-session-resume.md +12 -363
  44. claude_mpm/commands/mpm-status.md +5 -69
  45. claude_mpm/commands/mpm-ticket-view.md +52 -495
  46. claude_mpm/commands/mpm-version.md +5 -107
  47. claude_mpm/config/agent_sources.py +27 -0
  48. claude_mpm/core/config.py +2 -4
  49. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  50. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  51. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  52. claude_mpm/core/framework_loader.py +4 -2
  53. claude_mpm/core/logger.py +13 -0
  54. claude_mpm/core/optimized_startup.py +59 -0
  55. claude_mpm/core/shared/config_loader.py +1 -1
  56. claude_mpm/core/socketio_pool.py +3 -3
  57. claude_mpm/core/unified_agent_registry.py +5 -15
  58. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  74. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  75. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  76. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  79. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  80. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  81. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  82. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  83. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  84. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  85. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  86. claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
  87. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  88. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  89. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  90. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  91. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  92. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  93. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
  97. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  98. claude_mpm/hooks/memory_integration_hook.py +46 -1
  99. claude_mpm/init.py +63 -19
  100. claude_mpm/models/git_repository.py +3 -3
  101. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  102. claude_mpm/scripts/launch_monitor.py +93 -13
  103. claude_mpm/services/agents/agent_builder.py +3 -3
  104. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  105. claude_mpm/services/agents/agent_review_service.py +280 -0
  106. claude_mpm/services/agents/cache_git_manager.py +6 -6
  107. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  108. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -5
  109. claude_mpm/services/agents/deployment/agent_format_converter.py +23 -13
  110. claude_mpm/services/agents/deployment/agent_template_builder.py +32 -20
  111. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  112. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  113. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  114. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +247 -35
  115. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +392 -87
  116. claude_mpm/services/agents/git_source_manager.py +53 -4
  117. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  118. claude_mpm/services/agents/recommender.py +5 -3
  119. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  120. claude_mpm/services/agents/sources/git_source_sync_service.py +120 -7
  121. claude_mpm/services/agents/startup_sync.py +22 -2
  122. claude_mpm/services/agents/toolchain_detector.py +10 -6
  123. claude_mpm/services/analysis/__init__.py +11 -1
  124. claude_mpm/services/analysis/clone_detector.py +1030 -0
  125. claude_mpm/services/command_deployment_service.py +81 -10
  126. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  127. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  128. claude_mpm/services/event_bus/config.py +3 -1
  129. claude_mpm/services/git/git_operations_service.py +101 -16
  130. claude_mpm/services/monitor/daemon.py +9 -2
  131. claude_mpm/services/monitor/daemon_manager.py +39 -3
  132. claude_mpm/services/monitor/management/lifecycle.py +8 -1
  133. claude_mpm/services/monitor/server.py +698 -22
  134. claude_mpm/services/pm_skills_deployer.py +711 -0
  135. claude_mpm/services/profile_manager.py +331 -0
  136. claude_mpm/services/self_upgrade_service.py +120 -12
  137. claude_mpm/services/skills/__init__.py +3 -0
  138. claude_mpm/services/skills/git_skill_source_manager.py +130 -2
  139. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  140. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  141. claude_mpm/services/skills_deployer.py +127 -9
  142. claude_mpm/services/socketio/dashboard_server.py +1 -0
  143. claude_mpm/services/socketio/event_normalizer.py +51 -6
  144. claude_mpm/services/socketio/server/core.py +386 -108
  145. claude_mpm/services/version_control/git_operations.py +103 -0
  146. claude_mpm/skills/skill_manager.py +92 -3
  147. claude_mpm/utils/agent_dependency_loader.py +14 -2
  148. claude_mpm/utils/agent_filters.py +17 -44
  149. claude_mpm/utils/migration.py +4 -4
  150. claude_mpm/utils/robust_installer.py +47 -3
  151. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/METADATA +53 -87
  152. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/RECORD +157 -197
  153. claude_mpm-5.4.48.dist-info/entry_points.txt +5 -0
  154. claude_mpm-5.4.48.dist-info/licenses/LICENSE +94 -0
  155. claude_mpm-5.4.48.dist-info/licenses/LICENSE-FAQ.md +153 -0
  156. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  157. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  158. claude_mpm/agents/BASE_OPS.md +0 -219
  159. claude_mpm/agents/BASE_PM.md +0 -480
  160. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  161. claude_mpm/agents/BASE_QA.md +0 -167
  162. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  163. claude_mpm/agents/base_agent_loader.py +0 -601
  164. claude_mpm/cli/commands/agents_detect.py +0 -380
  165. claude_mpm/cli/commands/agents_recommend.py +0 -309
  166. claude_mpm/cli/ticket_cli.py +0 -35
  167. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  168. claude_mpm/commands/mpm-agents-detect.md +0 -177
  169. claude_mpm/commands/mpm-agents-list.md +0 -131
  170. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  171. claude_mpm/commands/mpm-config-view.md +0 -150
  172. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  173. claude_mpm/dashboard/analysis_runner.py +0 -455
  174. claude_mpm/dashboard/index.html +0 -13
  175. claude_mpm/dashboard/open_dashboard.py +0 -66
  176. claude_mpm/dashboard/static/css/activity.css +0 -1958
  177. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  178. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  179. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  180. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  181. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  182. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  183. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  184. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  185. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  186. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  187. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  188. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  189. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  190. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  191. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  192. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  193. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  194. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  195. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  196. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  197. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  198. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  199. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  200. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  201. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  202. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  203. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  204. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  205. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  206. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  207. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  208. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  209. claude_mpm/dashboard/templates/code_simple.html +0 -153
  210. claude_mpm/dashboard/templates/index.html +0 -606
  211. claude_mpm/dashboard/test_dashboard.html +0 -372
  212. claude_mpm/scripts/mcp_server.py +0 -75
  213. claude_mpm/scripts/mcp_wrapper.py +0 -39
  214. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  215. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  216. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  217. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  218. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  219. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  220. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  221. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  222. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  223. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  224. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  225. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  226. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  227. claude_mpm/services/mcp_gateway/main.py +0 -589
  228. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  229. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  230. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  231. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  232. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  233. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  234. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  235. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  236. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  237. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  238. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  239. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  240. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  241. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  242. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  243. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  244. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  245. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  246. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  247. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/WHEEL +0 -0
  248. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/top_level.txt +0 -0
@@ -551,38 +551,39 @@ class AsyncAgentDeploymentService:
551
551
  or ["Read", "Write", "Edit", "Grep", "Glob", "LS"] # Default fallback
552
552
  )
553
553
 
554
- # Get model from capabilities.model in new format
554
+ # Get model from capabilities.model in new format (no default fallback)
555
555
  model = (
556
556
  agent_data.get("capabilities", {}).get("model")
557
557
  or agent_data.get("configuration_fields", {}).get("model")
558
- or "sonnet" # Default fallback
558
+ # No default fallback - preserve None if not set
559
559
  )
560
560
 
561
- # Simplify model name for Claude Code
562
- model_map = {
563
- "claude-4-sonnet-20250514": "sonnet",
564
- "claude-sonnet-4-20250514": "sonnet",
565
- "claude-opus-4-20250514": "opus",
566
- "claude-3-opus-20240229": "opus",
567
- "claude-3-haiku-20240307": "haiku",
568
- "claude-3.5-sonnet": "sonnet",
569
- "claude-3-sonnet": "sonnet",
570
- }
571
- # Better fallback: extract the model type (opus/sonnet/haiku) from the string
572
- if model not in model_map:
573
- if "opus" in model.lower():
574
- model = "opus"
575
- elif "sonnet" in model.lower():
576
- model = "sonnet"
577
- elif "haiku" in model.lower():
578
- model = "haiku"
561
+ # Simplify model name for Claude Code (only if model is specified)
562
+ if model is not None:
563
+ model_map = {
564
+ "claude-4-sonnet-20250514": "sonnet",
565
+ "claude-sonnet-4-20250514": "sonnet",
566
+ "claude-opus-4-20250514": "opus",
567
+ "claude-3-opus-20240229": "opus",
568
+ "claude-3-haiku-20240307": "haiku",
569
+ "claude-3.5-sonnet": "sonnet",
570
+ "claude-3-sonnet": "sonnet",
571
+ }
572
+ # Better fallback: extract the model type (opus/sonnet/haiku) from the string
573
+ if model not in model_map:
574
+ if "opus" in model.lower():
575
+ model = "opus"
576
+ elif "sonnet" in model.lower():
577
+ model = "sonnet"
578
+ elif "haiku" in model.lower():
579
+ model = "haiku"
580
+ else:
581
+ # Last resort: try to extract from hyphenated format
582
+ model = model_map.get(
583
+ model, model.split("-")[-1] if "-" in model else model
584
+ )
579
585
  else:
580
- # Last resort: try to extract from hyphenated format
581
- model = model_map.get(
582
- model, model.split("-")[-1] if "-" in model else model
583
- )
584
- else:
585
- model = model_map[model]
586
+ model = model_map[model]
586
587
 
587
588
  # Convert tools list to comma-separated string for Claude Code compatibility
588
589
  # IMPORTANT: No spaces after commas - Claude Code requires exact format
@@ -601,9 +602,12 @@ class AsyncAgentDeploymentService:
601
602
  f"base_version: {self._format_version_display(base_version)}",
602
603
  "author: claude-mpm", # Identify as system agent for deployment
603
604
  f"tools: {tools_str}",
604
- f"model: {model}",
605
605
  ]
606
606
 
607
+ # Only include model field if explicitly set
608
+ if model is not None:
609
+ frontmatter_lines.append(f"model: {model}")
610
+
607
611
  # Add optional fields if present
608
612
  # Check for color in metadata section (new format) or root (old format)
609
613
  color = agent_data.get("metadata", {}).get("color") or agent_data.get("color")
@@ -157,7 +157,9 @@ class LocalTemplateDeploymentService:
157
157
 
158
158
  # Add capabilities
159
159
  if template.capabilities:
160
- frontmatter["model"] = template.capabilities.get("model", "sonnet")
160
+ # Only include model if explicitly set (no default)
161
+ if "model" in template.capabilities:
162
+ frontmatter["model"] = template.capabilities["model"]
161
163
  tools = template.capabilities.get("tools", "*")
162
164
  if tools == "*":
163
165
  frontmatter["tools"] = "all"
@@ -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
 
@@ -24,6 +26,18 @@ from .agent_version_manager import AgentVersionManager
24
26
  from .remote_agent_discovery_service import RemoteAgentDiscoveryService
25
27
 
26
28
 
29
+ def _normalize_agent_name(name: str) -> str:
30
+ """Normalize agent name for consistent comparison.
31
+
32
+ Converts spaces, underscores to hyphens and lowercases.
33
+ Examples:
34
+ "Dart Engineer" -> "dart-engineer"
35
+ "dart_engineer" -> "dart-engineer"
36
+ "DART-ENGINEER" -> "dart-engineer"
37
+ """
38
+ return name.lower().replace(" ", "-").replace("_", "-")
39
+
40
+
27
41
  class MultiSourceAgentDeploymentService:
28
42
  """Service for deploying agents from multiple sources with version comparison.
29
43
 
@@ -51,6 +65,70 @@ class MultiSourceAgentDeploymentService:
51
65
  self.logger = get_logger(__name__)
52
66
  self.version_manager = AgentVersionManager()
53
67
 
68
+ def _read_template_version(self, template_path: Path) -> Optional[str]:
69
+ """Read version from template file (supports both .md and .json formats).
70
+
71
+ For .md files: Extract version from YAML frontmatter
72
+ For .json files: Extract version from JSON structure
73
+
74
+ Args:
75
+ template_path: Path to template file
76
+
77
+ Returns:
78
+ Version string or None if version cannot be extracted
79
+ """
80
+ try:
81
+ if template_path.suffix == ".md":
82
+ # Parse markdown with YAML frontmatter
83
+ content = template_path.read_text()
84
+
85
+ # Extract YAML frontmatter (between --- markers)
86
+ if not content.strip().startswith("---"):
87
+ return None
88
+
89
+ parts = content.split("---", 2)
90
+ if len(parts) < 3:
91
+ return None
92
+
93
+ # Parse YAML frontmatter
94
+ frontmatter = yaml.safe_load(parts[1])
95
+ if not frontmatter:
96
+ return None
97
+
98
+ # Extract version from frontmatter
99
+ version = frontmatter.get("version")
100
+ return version if version else None
101
+
102
+ if template_path.suffix == ".json":
103
+ # Parse JSON template
104
+ template_data = json.loads(template_path.read_text())
105
+ metadata = template_data.get("metadata", {})
106
+ version = (
107
+ template_data.get("agent_version")
108
+ or template_data.get("version")
109
+ or metadata.get("version")
110
+ )
111
+ return version if version else None
112
+
113
+ self.logger.warning(
114
+ f"Unknown template format: {template_path.suffix} for {template_path.name}"
115
+ )
116
+ return None
117
+
118
+ except yaml.YAMLError as e:
119
+ self.logger.warning(
120
+ f"Invalid YAML frontmatter in {template_path.name}: {e}"
121
+ )
122
+ return None
123
+ except json.JSONDecodeError as e:
124
+ self.logger.warning(f"Invalid JSON in {template_path.name}: {e}")
125
+ return None
126
+ except Exception as e:
127
+ self.logger.warning(
128
+ f"Error reading template version from {template_path.name}: {e}"
129
+ )
130
+ return None
131
+
54
132
  def _build_canonical_id_for_agent(self, agent_info: Dict[str, Any]) -> str:
55
133
  """Build or retrieve canonical_id for an agent.
56
134
 
@@ -113,14 +191,14 @@ class MultiSourceAgentDeploymentService:
113
191
  system_templates_dir: Optional[Path] = None,
114
192
  project_agents_dir: Optional[Path] = None,
115
193
  user_agents_dir: Optional[Path] = None,
116
- remote_agents_dir: Optional[Path] = None,
194
+ agents_cache_dir: Optional[Path] = None,
117
195
  working_directory: Optional[Path] = None,
118
196
  ) -> Dict[str, List[Dict[str, Any]]]:
119
- """Discover agents from all 4 tiers (system, user, remote, project).
197
+ """Discover agents from all 4 tiers (system, user, cache, project).
120
198
 
121
199
  Priority hierarchy (highest to lowest):
122
200
  4. Project agents - Highest priority, project-specific customizations
123
- 3. Remote agents - GitHub-synced agents from cache
201
+ 3. Cached agents - GitHub-synced agents from cache
124
202
  2. User agents - DEPRECATED, user-level customizations
125
203
  1. System templates - Lowest priority, built-in agents
126
204
 
@@ -128,7 +206,7 @@ class MultiSourceAgentDeploymentService:
128
206
  system_templates_dir: Directory containing system agent templates
129
207
  project_agents_dir: Directory containing project-specific agents
130
208
  user_agents_dir: Directory containing user custom agents (DEPRECATED)
131
- remote_agents_dir: Directory containing cached remote agents
209
+ agents_cache_dir: Directory containing cached agents from Git sources
132
210
  working_directory: Current working directory for finding project agents
133
211
 
134
212
  Returns:
@@ -159,12 +237,12 @@ class MultiSourceAgentDeploymentService:
159
237
  if not user_agents_dir.exists():
160
238
  user_agents_dir = None
161
239
 
162
- if not remote_agents_dir:
163
- # Check for remote agents in cache directory
240
+ if not agents_cache_dir:
241
+ # Check for agents in cache directory
164
242
  cache_dir = Path.home() / ".claude-mpm" / "cache"
165
- remote_agents_dir = cache_dir / "remote-agents"
166
- if not remote_agents_dir.exists():
167
- remote_agents_dir = None
243
+ agents_cache_dir = cache_dir / "agents"
244
+ if not agents_cache_dir.exists():
245
+ agents_cache_dir = None
168
246
 
169
247
  # Discover agents from each source in priority order
170
248
  # Note: We process in reverse priority order (system first) and build up the dictionary
@@ -172,7 +250,7 @@ class MultiSourceAgentDeploymentService:
172
250
  sources = [
173
251
  ("system", system_templates_dir),
174
252
  ("user", user_agents_dir),
175
- ("remote", remote_agents_dir),
253
+ ("remote", agents_cache_dir),
176
254
  ("project", project_agents_dir),
177
255
  ]
178
256
 
@@ -257,7 +335,7 @@ class MultiSourceAgentDeploymentService:
257
335
  def get_agents_by_collection(
258
336
  self,
259
337
  collection_id: str,
260
- remote_agents_dir: Optional[Path] = None,
338
+ agents_cache_dir: Optional[Path] = None,
261
339
  ) -> List[Dict[str, Any]]:
262
340
  """Get all agents from a specific collection.
263
341
 
@@ -265,7 +343,7 @@ class MultiSourceAgentDeploymentService:
265
343
 
266
344
  Args:
267
345
  collection_id: Collection identifier (e.g., "bobmatnyc/claude-mpm-agents")
268
- remote_agents_dir: Directory containing remote agents cache
346
+ agents_cache_dir: Directory containing agents cache
269
347
 
270
348
  Returns:
271
349
  List of agent dictionaries from the specified collection
@@ -276,18 +354,16 @@ class MultiSourceAgentDeploymentService:
276
354
  >>> len(agents)
277
355
  45
278
356
  """
279
- if not remote_agents_dir:
357
+ if not agents_cache_dir:
280
358
  cache_dir = Path.home() / ".claude-mpm" / "cache"
281
- remote_agents_dir = cache_dir / "remote-agents"
359
+ agents_cache_dir = cache_dir / "agents"
282
360
 
283
- if not remote_agents_dir.exists():
284
- self.logger.warning(
285
- f"Remote agents directory not found: {remote_agents_dir}"
286
- )
361
+ if not agents_cache_dir.exists():
362
+ self.logger.warning(f"Agents cache directory not found: {agents_cache_dir}")
287
363
  return []
288
364
 
289
365
  # Use RemoteAgentDiscoveryService to get collection agents
290
- remote_service = RemoteAgentDiscoveryService(remote_agents_dir)
366
+ remote_service = RemoteAgentDiscoveryService(agents_cache_dir)
291
367
  collection_agents = remote_service.get_agents_by_collection(collection_id)
292
368
 
293
369
  self.logger.info(
@@ -404,7 +480,7 @@ class MultiSourceAgentDeploymentService:
404
480
  system_templates_dir: Optional[Path] = None,
405
481
  project_agents_dir: Optional[Path] = None,
406
482
  user_agents_dir: Optional[Path] = None,
407
- remote_agents_dir: Optional[Path] = None,
483
+ agents_cache_dir: Optional[Path] = None,
408
484
  working_directory: Optional[Path] = None,
409
485
  excluded_agents: Optional[List[str]] = None,
410
486
  config: Optional[Config] = None,
@@ -416,7 +492,7 @@ class MultiSourceAgentDeploymentService:
416
492
  system_templates_dir: Directory containing system agent templates
417
493
  project_agents_dir: Directory containing project-specific agents
418
494
  user_agents_dir: Directory containing user custom agents (DEPRECATED)
419
- remote_agents_dir: Directory containing cached remote agents
495
+ agents_cache_dir: Directory containing cached agents from Git sources
420
496
  working_directory: Current working directory for finding project agents
421
497
  excluded_agents: List of agent names to exclude from deployment
422
498
  config: Configuration object for additional filtering
@@ -433,7 +509,7 @@ class MultiSourceAgentDeploymentService:
433
509
  system_templates_dir=system_templates_dir,
434
510
  project_agents_dir=project_agents_dir,
435
511
  user_agents_dir=user_agents_dir,
436
- remote_agents_dir=remote_agents_dir,
512
+ agents_cache_dir=agents_cache_dir,
437
513
  working_directory=working_directory,
438
514
  )
439
515
 
@@ -467,10 +543,44 @@ class MultiSourceAgentDeploymentService:
467
543
 
468
544
  # Apply exclusion filters
469
545
  if excluded_agents:
470
- for agent_name in excluded_agents:
471
- if agent_name in selected_agents:
472
- self.logger.info(f"Excluding agent '{agent_name}' from deployment")
473
- del selected_agents[agent_name]
546
+ # Find agents to remove by matching normalized names
547
+ # Normalization handles: "Dart Engineer", "dart_engineer", "dart-engineer"
548
+ agents_to_remove = []
549
+ excluded_set = {_normalize_agent_name(name) for name in excluded_agents}
550
+
551
+ for canonical_id, agent_info in list(selected_agents.items()):
552
+ # Check agent name field (normalized)
553
+ agent_name = _normalize_agent_name(agent_info.get("name", ""))
554
+
555
+ # Also check the agent_id portion of canonical_id (after the colon)
556
+ # Example: "bobmatnyc/claude-mpm-agents:pm" -> "pm"
557
+ raw_agent_id = (
558
+ canonical_id.split(":")[-1]
559
+ if ":" in canonical_id
560
+ else canonical_id
561
+ )
562
+ agent_id = _normalize_agent_name(raw_agent_id)
563
+
564
+ # Check file stem from path (most reliable match)
565
+ file_stem = ""
566
+ path_str = agent_info.get("path") or agent_info.get("file_path")
567
+ if path_str:
568
+ file_stem = _normalize_agent_name(Path(path_str).stem)
569
+
570
+ if (
571
+ agent_name in excluded_set
572
+ or agent_id in excluded_set
573
+ or file_stem in excluded_set
574
+ ):
575
+ agents_to_remove.append(canonical_id)
576
+ self.logger.info(
577
+ f"Excluding agent '{agent_info.get('name', raw_agent_id)}' "
578
+ f"(canonical_id: {canonical_id}) from deployment"
579
+ )
580
+
581
+ # Remove matched agents
582
+ for canonical_id in agents_to_remove:
583
+ del selected_agents[canonical_id]
474
584
 
475
585
  # Apply config-based filtering if provided
476
586
  if config:
@@ -519,6 +629,105 @@ class MultiSourceAgentDeploymentService:
519
629
 
520
630
  return agents_to_deploy, agent_sources, cleanup_results
521
631
 
632
+ def cleanup_excluded_agents(
633
+ self,
634
+ deployed_agents_dir: Path,
635
+ agents_to_deploy: Dict[str, Path],
636
+ ) -> Dict[str, Any]:
637
+ """Remove agents from deployed directory that aren't in the deployment list.
638
+
639
+ Similar to skill cleanup logic, this removes agents that were previously
640
+ deployed but are no longer in the enabled agents list (e.g., filtered out
641
+ by profile configuration).
642
+
643
+ Args:
644
+ deployed_agents_dir: Directory containing deployed agents (~/.claude/agents)
645
+ agents_to_deploy: Dictionary mapping agent file stems to template paths
646
+
647
+ Returns:
648
+ Dictionary with cleanup results:
649
+ - removed: List of removed agent names
650
+ - errors: List of errors during cleanup
651
+ """
652
+ cleanup_results = {"removed": [], "errors": []}
653
+
654
+ # Safety check - only operate on deployed agents directory
655
+ if not deployed_agents_dir.exists():
656
+ self.logger.debug("Deployed agents directory does not exist, no cleanup needed")
657
+ return cleanup_results
658
+
659
+ # Build set of agent names that should exist (file stems without .md extension)
660
+ expected_agents = set(agents_to_deploy.keys())
661
+
662
+ try:
663
+ # Check each file in deployed_agents_dir
664
+ for item in deployed_agents_dir.iterdir():
665
+ # Only process .md files
666
+ if not item.is_file() or item.suffix != ".md":
667
+ continue
668
+
669
+ # Skip hidden files
670
+ if item.name.startswith("."):
671
+ continue
672
+
673
+ # Get agent name (file stem)
674
+ agent_name = item.stem
675
+
676
+ # Check if this agent should be kept
677
+ if agent_name not in expected_agents:
678
+ try:
679
+ # Security: Validate path is within deployed_agents_dir
680
+ resolved_item = item.resolve()
681
+ resolved_target = deployed_agents_dir.resolve()
682
+
683
+ if not str(resolved_item).startswith(str(resolved_target)):
684
+ self.logger.error(
685
+ f"Refusing to remove path outside target directory: {item}"
686
+ )
687
+ cleanup_results["errors"].append(
688
+ {
689
+ "agent": agent_name,
690
+ "error": "Path outside target directory",
691
+ }
692
+ )
693
+ continue
694
+
695
+ # Remove the agent file
696
+ item.unlink()
697
+ cleanup_results["removed"].append(agent_name)
698
+ self.logger.info(f"Removed excluded agent: {agent_name}")
699
+
700
+ except PermissionError as e:
701
+ error_msg = f"Permission denied removing {agent_name}: {e}"
702
+ self.logger.error(error_msg)
703
+ cleanup_results["errors"].append(
704
+ {"agent": agent_name, "error": error_msg}
705
+ )
706
+ except Exception as e:
707
+ error_msg = f"Error removing {agent_name}: {e}"
708
+ self.logger.error(error_msg)
709
+ cleanup_results["errors"].append(
710
+ {"agent": agent_name, "error": error_msg}
711
+ )
712
+
713
+ except Exception as e:
714
+ self.logger.error(f"Error during agent cleanup: {e}")
715
+ cleanup_results["errors"].append(
716
+ {"agent": "cleanup_process", "error": str(e)}
717
+ )
718
+
719
+ # Log cleanup summary
720
+ if cleanup_results["removed"]:
721
+ self.logger.info(
722
+ f"Cleanup complete: removed {len(cleanup_results['removed'])} excluded agents"
723
+ )
724
+ if cleanup_results["errors"]:
725
+ self.logger.warning(
726
+ f"Encountered {len(cleanup_results['errors'])} errors during cleanup"
727
+ )
728
+
729
+ return cleanup_results
730
+
522
731
  def cleanup_outdated_user_agents(
523
732
  self,
524
733
  agents_by_name: Dict[str, List[Dict[str, Any]]],
@@ -827,17 +1036,20 @@ class MultiSourceAgentDeploymentService:
827
1036
  comparison_results["needs_update"].append(agent_name)
828
1037
  continue
829
1038
 
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")
1039
+ # Read template version using format-aware helper
1040
+ version_string = self._read_template_version(template_path)
1041
+ if not version_string:
1042
+ self.logger.warning(
1043
+ f"Could not extract version from template for '{agent_name}', skipping"
838
1044
  )
1045
+ continue
1046
+
1047
+ try:
1048
+ template_version = self.version_manager.parse_version(version_string)
839
1049
  except Exception as e:
840
- self.logger.warning(f"Error reading template for '{agent_name}': {e}")
1050
+ self.logger.warning(
1051
+ f"Error parsing version '{version_string}' for '{agent_name}': {e}"
1052
+ )
841
1053
  continue
842
1054
 
843
1055
  # Read deployed version