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
@@ -15,6 +15,8 @@ from dataclasses import dataclass
15
15
  from pathlib import Path
16
16
  from typing import Any, Dict, List, Optional
17
17
 
18
+ import yaml
19
+
18
20
  from claude_mpm.core.logging_config import get_logger
19
21
 
20
22
  logger = get_logger(__name__)
@@ -40,7 +42,7 @@ class RemoteAgentMetadata:
40
42
  class RemoteAgentDiscoveryService:
41
43
  """Discovers and converts remote Markdown agents to JSON format.
42
44
 
43
- Remote agents are discovered from the cache directory (~/.claude-mpm/cache/remote-agents/)
45
+ Remote agents are discovered from the cache directory (~/.claude-mpm/cache/agents/)
44
46
  where they are stored as Markdown files. This service:
45
47
  1. Discovers all *.md files in the remote agents cache
46
48
  2. Parses Markdown frontmatter and content to extract metadata
@@ -59,20 +61,20 @@ class RemoteAgentDiscoveryService:
59
61
  - Flexibility: Supports optional sections with defaults
60
62
  """
61
63
 
62
- def __init__(self, remote_agents_dir: Path):
64
+ def __init__(self, agents_cache_dir: Path):
63
65
  """Initialize the remote agent discovery service.
64
66
 
65
67
  Args:
66
- remote_agents_dir: Directory containing cached remote agent Markdown files
68
+ agents_cache_dir: Directory containing cached agent Markdown files
67
69
  """
68
- self.remote_agents_dir = remote_agents_dir
70
+ self.agents_cache_dir = agents_cache_dir
69
71
  self.logger = get_logger(__name__)
70
72
 
71
73
  def _extract_collection_id_from_path(self, file_path: Path) -> Optional[str]:
72
74
  """Extract collection_id from repository path structure.
73
75
 
74
76
  Collection ID is derived from the repository path structure:
75
- ~/.claude-mpm/cache/remote-agents/{owner}/{repo}/agents/...
77
+ ~/.claude-mpm/cache/agents/{owner}/{repo}/agents/...
76
78
 
77
79
  Args:
78
80
  file_path: Absolute path to agent Markdown file
@@ -81,28 +83,29 @@ class RemoteAgentDiscoveryService:
81
83
  Collection ID in format "owner/repo-name" or None if not found
82
84
 
83
85
  Example:
84
- Input: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents/agents/pm.md
86
+ Input: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/agents/pm.md
85
87
  Output: "bobmatnyc/claude-mpm-agents"
86
88
  """
87
89
  try:
88
- # Find "remote-agents" in the path
90
+ # Find "agents" cache directory in the path (looking for .claude-mpm/cache/agents)
89
91
  path_parts = file_path.parts
90
- remote_agents_idx = -1
92
+ agents_cache_idx = -1
91
93
 
92
94
  for i, part in enumerate(path_parts):
93
- if part == "remote-agents":
94
- remote_agents_idx = i
95
+ # Look for cache/agents pattern
96
+ if part == "agents" and i > 0 and path_parts[i - 1] == "cache":
97
+ agents_cache_idx = i
95
98
  break
96
99
 
97
- if remote_agents_idx == -1 or remote_agents_idx + 2 >= len(path_parts):
100
+ if agents_cache_idx == -1 or agents_cache_idx + 2 >= len(path_parts):
98
101
  self.logger.debug(
99
102
  f"Could not extract collection_id from path: {file_path}"
100
103
  )
101
104
  return None
102
105
 
103
- # Extract owner and repo (next two parts after "remote-agents")
104
- owner = path_parts[remote_agents_idx + 1]
105
- repo = path_parts[remote_agents_idx + 2]
106
+ # Extract owner and repo (next two parts after "cache/agents")
107
+ owner = path_parts[agents_cache_idx + 1]
108
+ repo = path_parts[agents_cache_idx + 2]
106
109
 
107
110
  collection_id = f"{owner}/{repo}"
108
111
  self.logger.debug(f"Extracted collection_id: {collection_id}")
@@ -126,25 +129,26 @@ class RemoteAgentDiscoveryService:
126
129
  Relative path from repo root, or None if not found
127
130
 
128
131
  Example:
129
- Input: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents/agents/pm.md
132
+ Input: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/agents/pm.md
130
133
  Output: "agents/pm.md"
131
134
  """
132
135
  try:
133
- # Find "remote-agents" in the path
136
+ # Find "agents" cache directory in the path
134
137
  path_parts = file_path.parts
135
- remote_agents_idx = -1
138
+ agents_cache_idx = -1
136
139
 
137
140
  for i, part in enumerate(path_parts):
138
- if part == "remote-agents":
139
- remote_agents_idx = i
141
+ # Look for cache/agents pattern
142
+ if part == "agents" and i > 0 and path_parts[i - 1] == "cache":
143
+ agents_cache_idx = i
140
144
  break
141
145
 
142
- if remote_agents_idx == -1 or remote_agents_idx + 3 >= len(path_parts):
146
+ if agents_cache_idx == -1 or agents_cache_idx + 3 >= len(path_parts):
143
147
  return None
144
148
 
145
149
  # Path after owner/repo is the source path
146
- # remote-agents/{owner}/{repo}/{source_path}
147
- repo_root_idx = remote_agents_idx + 3
150
+ # cache/agents/{owner}/{repo}/{source_path}
151
+ repo_root_idx = agents_cache_idx + 3
148
152
  source_parts = path_parts[repo_root_idx:]
149
153
 
150
154
  return "/".join(source_parts)
@@ -153,6 +157,110 @@ class RemoteAgentDiscoveryService:
153
157
  self.logger.warning(f"Failed to extract source_path from {file_path}: {e}")
154
158
  return None
155
159
 
160
+ def _parse_yaml_frontmatter(self, content: str) -> Optional[Dict[str, Any]]:
161
+ """Parse YAML frontmatter from Markdown content.
162
+
163
+ Extracts YAML frontmatter delimited by --- markers at the start of the file.
164
+ Uses a tolerant approach: attempts full YAML parsing first, falls back to
165
+ simple key-value extraction for malformed YAML.
166
+
167
+ Design Decision: Tolerant YAML Parsing
168
+
169
+ Rationale: Some agent markdown files have malformed YAML (incorrect indentation
170
+ in nested structures). Rather than failing completely, we:
171
+ 1. Try full YAML parsing first (handles well-formed YAML)
172
+ 2. Fall back to regex extraction for critical fields (agent_id, name, etc.)
173
+ 3. Log warnings but continue processing
174
+
175
+ This ensures we can still extract agent_id even if complex nested structures
176
+ (like template_changelog) have indentation issues.
177
+
178
+ Args:
179
+ content: Full Markdown file content
180
+
181
+ Returns:
182
+ Dictionary of parsed YAML frontmatter, or None if not found
183
+
184
+ Example:
185
+ Input:
186
+ ---
187
+ agent_id: python-engineer
188
+ name: Python Engineer
189
+ version: 2.3.0
190
+ ---
191
+ # Agent content...
192
+
193
+ Output:
194
+ {"agent_id": "python-engineer", "name": "Python Engineer", "version": "2.3.0"}
195
+ """
196
+ try:
197
+ # Check if content starts with YAML frontmatter
198
+ if not content.startswith("---"):
199
+ self.logger.debug("No YAML frontmatter found (doesn't start with ---)")
200
+ return None
201
+
202
+ # Extract frontmatter content between --- markers
203
+ frontmatter_match = re.match(r"^---\n(.*?)\n---\s*\n", content, re.DOTALL)
204
+ if not frontmatter_match:
205
+ self.logger.debug("No closing --- marker found for YAML frontmatter")
206
+ return None
207
+
208
+ yaml_content = frontmatter_match.group(1)
209
+
210
+ # Try full YAML parsing first
211
+ try:
212
+ parsed = yaml.safe_load(yaml_content)
213
+ if isinstance(parsed, dict):
214
+ return parsed
215
+ self.logger.warning(
216
+ f"YAML frontmatter is not a dictionary: {type(parsed)}"
217
+ )
218
+ except yaml.YAMLError as e:
219
+ # Malformed YAML (e.g., indentation errors) - fall back to regex extraction
220
+ self.logger.debug(
221
+ f"Full YAML parse failed, using fallback extraction: {e}"
222
+ )
223
+
224
+ # Extract key fields using regex (tolerant of malformed nested structures)
225
+ result = {}
226
+
227
+ # Extract simple key-value pairs (no nested structures)
228
+ simple_keys = [
229
+ "agent_id",
230
+ "name",
231
+ "description",
232
+ "version",
233
+ "model",
234
+ "agent_type",
235
+ "category",
236
+ "author",
237
+ "schema_version",
238
+ ]
239
+
240
+ for key in simple_keys:
241
+ # Match key: value on a line (not indented, so it's top-level)
242
+ pattern = rf"^{key}:\s*(.+?)$"
243
+ match = re.search(pattern, yaml_content, re.MULTILINE)
244
+ if match:
245
+ value = match.group(1).strip()
246
+ # Remove quotes if present
247
+ if value.startswith(("'", '"')) and value.endswith(("'", '"')):
248
+ value = value[1:-1]
249
+ result[key] = value
250
+
251
+ if result:
252
+ self.logger.debug(
253
+ f"Extracted {len(result)} fields using fallback method"
254
+ )
255
+ return result
256
+ return None
257
+
258
+ except Exception as e:
259
+ self.logger.warning(f"Unexpected error parsing frontmatter: {e}")
260
+ return None
261
+
262
+ return None
263
+
156
264
  def _generate_hierarchical_id(self, file_path: Path) -> str:
157
265
  """Generate hierarchical agent ID from file path.
158
266
 
@@ -165,14 +273,20 @@ class RemoteAgentDiscoveryService:
165
273
  - Preset matching against AUTO-DEPLOY-INDEX.md
166
274
  - Multi-level organization without name collisions
167
275
 
168
- Bug #4 Fix: Calculate relative to /agents/ subdirectory, not repository root
169
- This ensures agent IDs are based on their position within the agents directory.
276
+ Supports both cache structures:
277
+ 1. Git repo: Calculate relative to /agents/ subdirectory
278
+ 2. Flattened cache: Calculate relative to agents_cache_dir directly
170
279
 
171
- Example:
280
+ Example (Git repo):
172
281
  Input: /cache/bobmatnyc/claude-mpm-agents/agents/engineer/backend/python-engineer.md
173
282
  Root: /cache/bobmatnyc/claude-mpm-agents/agents
174
283
  Output: engineer/backend/python-engineer
175
284
 
285
+ Example (Flattened cache):
286
+ Input: /cache/agents/engineer/python-engineer.md
287
+ Root: /cache/agents
288
+ Output: engineer/python-engineer
289
+
176
290
  Args:
177
291
  file_path: Absolute path to agent Markdown file
178
292
 
@@ -180,16 +294,30 @@ class RemoteAgentDiscoveryService:
180
294
  Hierarchical agent ID with forward slashes
181
295
  """
182
296
  try:
183
- # Calculate relative to /agents/ subdirectory (Bug #4 fix)
184
- agents_dir = self.remote_agents_dir / "agents"
185
- relative_path = file_path.relative_to(agents_dir)
186
-
187
- # Remove .md extension and convert to forward slashes
188
- return str(relative_path.with_suffix("")).replace("\\", "/")
189
- except ValueError:
190
- # File is not under agents subdirectory, fall back to filename
297
+ # Try git repo structure first: /agents/ subdirectory
298
+ agents_dir = self.agents_cache_dir / "agents"
299
+ if agents_dir.exists():
300
+ try:
301
+ relative_path = file_path.relative_to(agents_dir)
302
+ return str(relative_path.with_suffix("")).replace("\\", "/")
303
+ except ValueError:
304
+ pass # Not under agents_dir, try flattened structure
305
+
306
+ # Try flattened cache structure: calculate relative to agents_cache_dir
307
+ try:
308
+ relative_path = file_path.relative_to(self.agents_cache_dir)
309
+ return str(relative_path.with_suffix("")).replace("\\", "/")
310
+ except ValueError:
311
+ pass # Not under agents_cache_dir either
312
+
313
+ # Fall back to filename
191
314
  self.logger.warning(
192
- f"File {file_path} not under agents directory, using filename"
315
+ f"File {file_path} not under expected directories, using filename"
316
+ )
317
+ return file_path.stem
318
+ except Exception as e:
319
+ self.logger.warning(
320
+ f"Error generating hierarchical ID for {file_path}: {e}"
193
321
  )
194
322
  return file_path.stem
195
323
 
@@ -199,14 +327,20 @@ class RemoteAgentDiscoveryService:
199
327
  Extracts category from directory structure. Category is the path
200
328
  from agents subdirectory to the file, excluding the filename.
201
329
 
202
- Bug #4 Fix: Calculate relative to /agents/ subdirectory, not repository root
203
- This ensures categories are based on agent organization within /agents/.
330
+ Supports both cache structures:
331
+ 1. Git repo: Calculate relative to /agents/ subdirectory
332
+ 2. Flattened cache: Calculate relative to agents_cache_dir directly
204
333
 
205
- Example:
334
+ Example (Git repo):
206
335
  Input: /cache/bobmatnyc/claude-mpm-agents/agents/engineer/backend/python-engineer.md
207
336
  Root: /cache/bobmatnyc/claude-mpm-agents/agents
208
337
  Output: engineer/backend
209
338
 
339
+ Example (Flattened cache):
340
+ Input: /cache/agents/engineer/python-engineer.md
341
+ Root: /cache/agents
342
+ Output: engineer
343
+
210
344
  Args:
211
345
  file_path: Absolute path to agent Markdown file
212
346
 
@@ -214,12 +348,26 @@ class RemoteAgentDiscoveryService:
214
348
  Category path with forward slashes, or "universal" if in root
215
349
  """
216
350
  try:
217
- # Calculate relative to /agents/ subdirectory (Bug #4 fix)
218
- agents_dir = self.remote_agents_dir / "agents"
219
- relative_path = file_path.relative_to(agents_dir)
220
- parts = relative_path.parts[:-1] # Exclude filename
221
- return "/".join(parts) if parts else "universal"
222
- except ValueError:
351
+ # Try git repo structure first: /agents/ subdirectory
352
+ agents_dir = self.agents_cache_dir / "agents"
353
+ if agents_dir.exists():
354
+ try:
355
+ relative_path = file_path.relative_to(agents_dir)
356
+ parts = relative_path.parts[:-1] # Exclude filename
357
+ return "/".join(parts) if parts else "universal"
358
+ except ValueError:
359
+ pass # Not under agents_dir, try flattened structure
360
+
361
+ # Try flattened cache structure: calculate relative to agents_cache_dir
362
+ try:
363
+ relative_path = file_path.relative_to(self.agents_cache_dir)
364
+ parts = relative_path.parts[:-1] # Exclude filename
365
+ return "/".join(parts) if parts else "universal"
366
+ except ValueError:
367
+ pass # Not under agents_cache_dir either
368
+
369
+ return "universal"
370
+ except Exception:
223
371
  return "universal"
224
372
 
225
373
  def discover_remote_agents(self) -> List[Dict[str, Any]]:
@@ -228,14 +376,18 @@ class RemoteAgentDiscoveryService:
228
376
  Scans the remote agents directory for *.md files recursively and converts each
229
377
  to JSON template format. Skips files that can't be parsed.
230
378
 
231
- Bug #4 Fix: Only scan /agents/ subdirectory, not entire repository
232
- This prevents README.md, CHANGELOG.md, etc. from being treated as agents.
379
+ Supports two cache structures:
380
+ 1. Git repo path: {path}/agents/ - has /agents/ subdirectory
381
+ 2. Flattened cache: {path}/ - directly contains category directories
382
+
383
+ Bug #4 Fix: Only scan /agents/ subdirectory when it exists to prevent
384
+ README.md, CHANGELOG.md, etc. from being treated as agents.
233
385
 
234
386
  Returns:
235
387
  List of agent dictionaries in JSON template format
236
388
 
237
389
  Example:
238
- >>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/remote-agents"))
390
+ >>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/agents"))
239
391
  >>> agents = service.discover_remote_agents()
240
392
  >>> len(agents)
241
393
  5
@@ -244,26 +396,134 @@ class RemoteAgentDiscoveryService:
244
396
  """
245
397
  agents = []
246
398
 
247
- if not self.remote_agents_dir.exists():
399
+ if not self.agents_cache_dir.exists():
248
400
  self.logger.debug(
249
- f"Remote agents directory does not exist: {self.remote_agents_dir}"
401
+ f"Agents cache directory does not exist: {self.agents_cache_dir}"
250
402
  )
251
403
  return agents
252
404
 
253
- # Bug #4 Fix: Only scan /agents/ subdirectory, not entire repository
254
- # This prevents non-agent files (README.md, CHANGELOG.md, etc.) from polluting results
255
- agents_dir = self.remote_agents_dir / "agents"
405
+ # Support four cache structures (PRIORITY ORDER):
406
+ # 1. Built output: {path}/dist/agents/ - PREFERRED (built with BASE-AGENT composition)
407
+ # 2. Git repo path: {path}/agents/ - source files (fallback)
408
+ # 3. Owner/repo structure: {path}/{owner}/{repo}/agents/ - GitHub sync structure
409
+ # 4. Flattened cache: {path}/ - directly contains category directories (legacy)
410
+
411
+ # Priority 1: Check for dist/agents/ (built output with BASE-AGENT composition)
412
+ dist_agents_dir = self.agents_cache_dir / "dist" / "agents"
413
+ agents_dir = self.agents_cache_dir / "agents"
414
+
415
+ if dist_agents_dir.exists():
416
+ # PREFERRED: Use built agents from dist/agents/
417
+ # These have BASE-AGENT.md files properly composed by build-agent.py
418
+ self.logger.debug(f"Using built agents from dist: {dist_agents_dir}")
419
+ scan_dir = dist_agents_dir
420
+ elif agents_dir.exists():
421
+ # FALLBACK: Git repo structure - scan /agents/ subdirectory (source files)
422
+ # This path is used when dist/agents/ hasn't been built yet
423
+ self.logger.debug(f"Using source agents (no dist/ found): {agents_dir}")
424
+ scan_dir = agents_dir
425
+ else:
426
+ # Priority 3: Check for {owner}/{repo}/agents/ structure (GitHub sync)
427
+ # e.g., ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/agents/
428
+ owner_repo_agents_dir = None
429
+ for owner_dir in self.agents_cache_dir.iterdir():
430
+ if owner_dir.is_dir() and not owner_dir.name.startswith("."):
431
+ for repo_dir in owner_dir.iterdir():
432
+ if repo_dir.is_dir():
433
+ potential_agents = repo_dir / "agents"
434
+ if potential_agents.exists():
435
+ owner_repo_agents_dir = potential_agents
436
+ self.logger.debug(
437
+ f"Using GitHub sync structure: {owner_repo_agents_dir}"
438
+ )
439
+ break
440
+ if owner_repo_agents_dir:
441
+ break
442
+
443
+ if owner_repo_agents_dir:
444
+ scan_dir = owner_repo_agents_dir
445
+ else:
446
+ # LEGACY: Flattened cache structure - scan root directly
447
+ # Check if this looks like the flattened cache (has category subdirectories)
448
+ category_dirs = [
449
+ "universal",
450
+ "engineer",
451
+ "ops",
452
+ "qa",
453
+ "security",
454
+ "documentation",
455
+ ]
456
+ has_categories = any(
457
+ (self.agents_cache_dir / cat).exists() for cat in category_dirs
458
+ )
256
459
 
257
- if not agents_dir.exists():
258
- self.logger.warning(
259
- f"Agents subdirectory not found: {agents_dir}. "
260
- f"Expected agents to be in /agents/ subdirectory."
261
- )
262
- return agents
460
+ if has_categories:
461
+ self.logger.debug(
462
+ f"Using flattened cache structure: {self.agents_cache_dir}"
463
+ )
464
+ scan_dir = self.agents_cache_dir
465
+ else:
466
+ self.logger.warning(
467
+ f"No agent directories found. Checked: {dist_agents_dir}, {agents_dir}, "
468
+ f"owner/repo/agents/ structure, and category directories in {self.agents_cache_dir}. "
469
+ f"Expected agents in /dist/agents/, /agents/, {owner}/{repo}/agents/, or category directories."
470
+ )
471
+ return agents
472
+
473
+ # Find all Markdown files recursively
474
+ md_files = list(scan_dir.rglob("*.md"))
475
+
476
+ # Filter out non-agent files and git repository files
477
+ excluded_files = {
478
+ "README.md",
479
+ "CHANGELOG.md",
480
+ "CONTRIBUTING.md",
481
+ "LICENSE.md",
482
+ "BASE-AGENT.md",
483
+ "SUMMARY.md",
484
+ "IMPLEMENTATION-SUMMARY.md",
485
+ "REFACTORING_REPORT.md",
486
+ "REORGANIZATION-PLAN.md",
487
+ "AUTO-DEPLOY-INDEX.md",
488
+ "PHASE1_COMPLETE.md",
489
+ "AGENTS.md",
490
+ # Skill-related files (should not be treated as agents)
491
+ "SKILL.md",
492
+ "SKILLS.md",
493
+ "skill-template.md",
494
+ }
495
+ md_files = [f for f in md_files if f.name not in excluded_files]
496
+
497
+ # Filter out files from skills-related directories
498
+ # Skills are not agents and should not be discovered here
499
+ excluded_directory_patterns = {"references", "examples", "claude-mpm-skills"}
500
+ md_files = [
501
+ f
502
+ for f in md_files
503
+ if not any(excluded in f.parts for excluded in excluded_directory_patterns)
504
+ ]
263
505
 
264
- # Find all Markdown files recursively in /agents/ subdirectory only
265
- md_files = list(agents_dir.rglob("*.md"))
266
- self.logger.debug(f"Found {len(md_files)} Markdown files in {agents_dir}")
506
+ # In flattened cache mode, also exclude files from git repository subdirectories
507
+ # (files under directories that contain .git folder)
508
+ if scan_dir == self.agents_cache_dir:
509
+ filtered_files = []
510
+ for f in md_files:
511
+ # Check if this file is inside a git repository (has .git in path)
512
+ # Git repos are at {agents_cache_dir}/{owner}/{repo}/.git
513
+ path_parts = f.relative_to(self.agents_cache_dir).parts
514
+ if len(path_parts) >= 2:
515
+ # Check if this looks like a git repo path (owner/repo)
516
+ potential_repo = (
517
+ self.agents_cache_dir / path_parts[0] / path_parts[1]
518
+ )
519
+ if (potential_repo / ".git").exists():
520
+ # This file is in a git repo, skip it (we'll handle git repos separately)
521
+ self.logger.debug(f"Skipping file in git repo: {f}")
522
+ continue
523
+ filtered_files.append(f)
524
+ md_files = filtered_files
525
+
526
+ self.logger.debug(f"Found {len(md_files)} Markdown files in {scan_dir}")
267
527
 
268
528
  for md_file in md_files:
269
529
  try:
@@ -281,15 +541,21 @@ class RemoteAgentDiscoveryService:
281
541
  self.logger.warning(f"Failed to parse remote agent {md_file.name}: {e}")
282
542
 
283
543
  self.logger.info(
284
- f"Discovered {len(agents)} remote agents from {self.remote_agents_dir.name}"
544
+ f"Discovered {len(agents)} remote agents from {self.agents_cache_dir.name}"
285
545
  )
286
546
  return agents
287
547
 
288
548
  def _parse_markdown_agent(self, md_file: Path) -> Optional[Dict[str, Any]]:
289
549
  """Parse Markdown agent file and convert to JSON template format.
290
550
 
291
- Expected Markdown format:
551
+ Expected Markdown format with YAML frontmatter:
292
552
  ```markdown
553
+ ---
554
+ agent_id: python-engineer
555
+ name: Python Engineer
556
+ version: 2.3.0
557
+ model: sonnet
558
+ ---
293
559
  # Agent Name
294
560
 
295
561
  Description paragraph (first paragraph after heading)
@@ -303,6 +569,11 @@ class RemoteAgentDiscoveryService:
303
569
  - Paths: /path1/, /path2/
304
570
  ```
305
571
 
572
+ Agent ID Priority (Mismatch Fix):
573
+ 1. Use agent_id from YAML frontmatter if present (e.g., "python-engineer")
574
+ 2. Fall back to leaf filename if no YAML frontmatter (e.g., "python-engineer.md" -> "python-engineer")
575
+ 3. Store hierarchical path separately as category_path for categorization
576
+
306
577
  Args:
307
578
  md_file: Path to Markdown agent file
308
579
 
@@ -320,22 +591,54 @@ class RemoteAgentDiscoveryService:
320
591
  self.logger.error(f"Failed to read file {md_file}: {e}")
321
592
  return None
322
593
 
323
- # Extract agent name from first heading
324
- name_match = re.search(r"^#\s+(.+?)$", content, re.MULTILINE)
325
- if not name_match:
326
- self.logger.debug(f"No agent name heading found in {md_file.name}")
327
- return None
328
- name = name_match.group(1).strip()
594
+ # MISMATCH FIX: Parse YAML frontmatter to extract agent_id
595
+ frontmatter = self._parse_yaml_frontmatter(content)
329
596
 
330
- # Extract description (first paragraph after heading, before next heading)
331
- desc_match = re.search(
332
- r"^#.+?\n\n(.+?)(?:\n\n##|\Z)", content, re.DOTALL | re.MULTILINE
333
- )
334
- description = desc_match.group(1).strip() if desc_match else ""
597
+ # MISMATCH FIX: Use agent_id from YAML frontmatter if present, otherwise fall back to filename
598
+ if frontmatter and "agent_id" in frontmatter:
599
+ agent_id = frontmatter["agent_id"]
600
+ self.logger.debug(f"Using agent_id from YAML frontmatter: {agent_id}")
601
+ else:
602
+ # Fallback: Use leaf filename without extension
603
+ agent_id = md_file.stem
604
+ self.logger.debug(f"No agent_id in YAML, using filename: {agent_id}")
335
605
 
336
- # Extract model from Configuration section
337
- model_match = re.search(r"Model:\s*(\w+)", content, re.IGNORECASE)
338
- model = model_match.group(1) if model_match else "sonnet"
606
+ # Store hierarchical path separately for categorization (not as primary ID)
607
+ hierarchical_path = self._generate_hierarchical_id(md_file)
608
+
609
+ # Extract agent name - prioritize frontmatter over markdown heading
610
+ # Frontmatter is intentional metadata, headings may be arbitrary content
611
+ if frontmatter and "name" in frontmatter:
612
+ name = frontmatter["name"]
613
+ else:
614
+ # Fallback to first heading or filename
615
+ name_match = re.search(r"^#\s+(.+?)$", content, re.MULTILINE)
616
+ if name_match:
617
+ name = name_match.group(1).strip()
618
+ else:
619
+ # Last resort: derive from filename
620
+ name = md_file.stem.replace("-", " ").replace("_", " ").title()
621
+
622
+ # Extract description - prioritize frontmatter over markdown content
623
+ # Frontmatter is intentional metadata, paragraphs may be arbitrary content
624
+ if frontmatter and "description" in frontmatter:
625
+ description = frontmatter["description"]
626
+ else:
627
+ # Fallback to first paragraph after heading
628
+ desc_match = re.search(
629
+ r"^#.+?\n\n(.+?)(?:\n\n##|\Z)", content, re.DOTALL | re.MULTILINE
630
+ )
631
+ if desc_match:
632
+ description = desc_match.group(1).strip()
633
+ else:
634
+ description = ""
635
+
636
+ # Extract model from YAML frontmatter or Configuration section
637
+ if frontmatter and "model" in frontmatter:
638
+ model = frontmatter["model"]
639
+ else:
640
+ model_match = re.search(r"Model:\s*(\w+)", content, re.IGNORECASE)
641
+ model = model_match.group(1) if model_match else "sonnet"
339
642
 
340
643
  # Extract priority from Configuration section
341
644
  priority_match = re.search(r"Priority:\s*(\d+)", content, re.IGNORECASE)
@@ -353,12 +656,11 @@ class RemoteAgentDiscoveryService:
353
656
  if paths_match:
354
657
  paths = [p.strip() for p in paths_match.group(1).split(",")]
355
658
 
356
- # Get version (SHA-256 hash) from cache metadata
357
- version = self._get_agent_version(md_file)
358
-
359
- # Bug #3 fix: Generate hierarchical agent_id from file path
360
- # This preserves directory structure for category filtering and preset matching
361
- agent_id = self._generate_hierarchical_id(md_file)
659
+ # Get version (SHA-256 hash) from cache metadata or YAML frontmatter
660
+ if frontmatter and "version" in frontmatter:
661
+ version = frontmatter["version"]
662
+ else:
663
+ version = self._get_agent_version(md_file)
362
664
 
363
665
  # Bug #1 fix: Detect category from directory path
364
666
  category = self._detect_category_from_path(md_file)
@@ -368,6 +670,7 @@ class RemoteAgentDiscoveryService:
368
670
  source_path = self._extract_source_path_from_file(md_file)
369
671
 
370
672
  # NEW: Generate canonical_id (collection_id:agent_id)
673
+ # Use leaf agent_id (not hierarchical path) for canonical_id
371
674
  if collection_id:
372
675
  canonical_id = f"{collection_id}:{agent_id}"
373
676
  else:
@@ -378,8 +681,9 @@ class RemoteAgentDiscoveryService:
378
681
  # IMPORTANT: Include 'path' field for compatibility with deployment validation (ticket 1M-480)
379
682
  # Git-sourced agents must have 'path' field to match structure from AgentDiscoveryService
380
683
  return {
381
- "agent_id": agent_id,
382
- "canonical_id": canonical_id, # NEW: Primary matching key
684
+ "agent_id": agent_id, # MISMATCH FIX: Use leaf name from YAML, not hierarchical path
685
+ "hierarchical_path": hierarchical_path, # Store hierarchical path separately
686
+ "canonical_id": canonical_id, # NEW: Primary matching key (uses leaf agent_id)
383
687
  "collection_id": collection_id, # NEW: Collection identifier
384
688
  "source_path": source_path, # NEW: Path within repository
385
689
  "metadata": {
@@ -388,6 +692,7 @@ class RemoteAgentDiscoveryService:
388
692
  "version": version,
389
693
  "author": "remote", # Mark as remote agent
390
694
  "category": category, # Use detected category from path
695
+ "hierarchical_path": hierarchical_path, # For categorization/filtering
391
696
  "collection_id": collection_id, # NEW: Also in metadata
392
697
  "source_path": source_path, # NEW: Also in metadata
393
698
  "canonical_id": canonical_id, # NEW: Also in metadata
@@ -453,7 +758,7 @@ class RemoteAgentDiscoveryService:
453
758
  RemoteAgentMetadata if found, None otherwise
454
759
  """
455
760
  # Bug #4 fix: Search in /agents/ subdirectory, not root directory
456
- agents_dir = self.remote_agents_dir / "agents"
761
+ agents_dir = self.agents_cache_dir / "agents"
457
762
  if not agents_dir.exists():
458
763
  return None
459
764
 
@@ -485,7 +790,7 @@ class RemoteAgentDiscoveryService:
485
790
  List of agent dictionaries from the specified collection
486
791
 
487
792
  Example:
488
- >>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/remote-agents"))
793
+ >>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/agents"))
489
794
  >>> agents = service.get_agents_by_collection("bobmatnyc/claude-mpm-agents")
490
795
  >>> len(agents)
491
796
  45
@@ -513,7 +818,7 @@ class RemoteAgentDiscoveryService:
513
818
  - agents: List of agent IDs in collection
514
819
 
515
820
  Example:
516
- >>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/remote-agents"))
821
+ >>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/agents"))
517
822
  >>> collections = service.list_collections()
518
823
  >>> collections
519
824
  [