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.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +4 -0
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +843 -900
- claude_mpm/agents/WORKFLOW.md +5 -254
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +2 -2
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +18 -27
- claude_mpm/cli/commands/agents.py +9 -40
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1098 -159
- claude_mpm/cli/commands/configure_agent_display.py +25 -6
- claude_mpm/cli/commands/mpm_init/core.py +225 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/postmortem.py +1 -1
- claude_mpm/cli/commands/profile.py +277 -0
- claude_mpm/cli/commands/skills.py +218 -197
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +21 -3
- claude_mpm/cli/interactive/agent_wizard.py +2 -2
- claude_mpm/cli/parsers/agents_parser.py +0 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
- claude_mpm/cli/parsers/base_parser.py +12 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/profile_parser.py +148 -0
- claude_mpm/cli/parsers/skills_parser.py +0 -5
- claude_mpm/cli/startup.py +876 -149
- claude_mpm/commands/mpm-config.md +28 -0
- claude_mpm/commands/mpm-doctor.md +9 -22
- claude_mpm/commands/mpm-help.md +5 -287
- claude_mpm/commands/mpm-init.md +81 -507
- claude_mpm/commands/mpm-monitor.md +15 -402
- claude_mpm/commands/mpm-organize.md +120 -0
- claude_mpm/commands/mpm-postmortem.md +6 -108
- claude_mpm/commands/mpm-session-resume.md +12 -363
- claude_mpm/commands/mpm-status.md +5 -69
- claude_mpm/commands/mpm-ticket-view.md +52 -495
- claude_mpm/commands/mpm-version.md +5 -107
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/config.py +2 -4
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/optimized_startup.py +59 -0
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +5 -15
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
- claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
- claude_mpm/dashboard/static/svelte-build/index.html +36 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +63 -19
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/cache_git_manager.py +6 -6
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -5
- claude_mpm/services/agents/deployment/agent_format_converter.py +23 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +32 -20
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +247 -35
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +392 -87
- claude_mpm/services/agents/git_source_manager.py +53 -4
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/recommender.py +5 -3
- claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
- claude_mpm/services/agents/sources/git_source_sync_service.py +120 -7
- claude_mpm/services/agents/startup_sync.py +22 -2
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +101 -16
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/management/lifecycle.py +8 -1
- claude_mpm/services/monitor/server.py +698 -22
- claude_mpm/services/pm_skills_deployer.py +711 -0
- claude_mpm/services/profile_manager.py +331 -0
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +130 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +127 -9
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +51 -6
- claude_mpm/services/socketio/server/core.py +386 -108
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/skills/skill_manager.py +92 -3
- claude_mpm/utils/agent_dependency_loader.py +14 -2
- claude_mpm/utils/agent_filters.py +17 -44
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +47 -3
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/METADATA +53 -87
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/RECORD +157 -197
- claude_mpm-5.4.48.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.48.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.48.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/WHEEL +0 -0
- {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/
|
|
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,
|
|
64
|
+
def __init__(self, agents_cache_dir: Path):
|
|
63
65
|
"""Initialize the remote agent discovery service.
|
|
64
66
|
|
|
65
67
|
Args:
|
|
66
|
-
|
|
68
|
+
agents_cache_dir: Directory containing cached agent Markdown files
|
|
67
69
|
"""
|
|
68
|
-
self.
|
|
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/
|
|
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/
|
|
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 "
|
|
90
|
+
# Find "agents" cache directory in the path (looking for .claude-mpm/cache/agents)
|
|
89
91
|
path_parts = file_path.parts
|
|
90
|
-
|
|
92
|
+
agents_cache_idx = -1
|
|
91
93
|
|
|
92
94
|
for i, part in enumerate(path_parts):
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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 "
|
|
104
|
-
owner = path_parts[
|
|
105
|
-
repo = path_parts[
|
|
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/
|
|
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 "
|
|
136
|
+
# Find "agents" cache directory in the path
|
|
134
137
|
path_parts = file_path.parts
|
|
135
|
-
|
|
138
|
+
agents_cache_idx = -1
|
|
136
139
|
|
|
137
140
|
for i, part in enumerate(path_parts):
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
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
|
-
#
|
|
147
|
-
repo_root_idx =
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
#
|
|
184
|
-
agents_dir = self.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
#
|
|
218
|
-
agents_dir = self.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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/
|
|
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.
|
|
399
|
+
if not self.agents_cache_dir.exists():
|
|
248
400
|
self.logger.debug(
|
|
249
|
-
f"
|
|
401
|
+
f"Agents cache directory does not exist: {self.agents_cache_dir}"
|
|
250
402
|
)
|
|
251
403
|
return agents
|
|
252
404
|
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
#
|
|
265
|
-
|
|
266
|
-
|
|
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.
|
|
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
|
-
#
|
|
324
|
-
|
|
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
|
-
#
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
#
|
|
337
|
-
|
|
338
|
-
|
|
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
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
"
|
|
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.
|
|
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/
|
|
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/
|
|
821
|
+
>>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/agents"))
|
|
517
822
|
>>> collections = service.list_collections()
|
|
518
823
|
>>> collections
|
|
519
824
|
[
|