claude-mpm 5.1.9__py3-none-any.whl → 5.4.14__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 (162) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +85 -0
  4. claude_mpm/agents/agent_loader.py +13 -44
  5. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  6. claude_mpm/cli/__main__.py +4 -0
  7. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  8. claude_mpm/cli/commands/auto_configure.py +210 -25
  9. claude_mpm/cli/commands/config.py +88 -2
  10. claude_mpm/cli/commands/configure.py +1097 -158
  11. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  12. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  13. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  14. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  15. claude_mpm/cli/commands/skills.py +21 -2
  16. claude_mpm/cli/commands/summarize.py +413 -0
  17. claude_mpm/cli/executor.py +11 -3
  18. claude_mpm/cli/parsers/base_parser.py +5 -0
  19. claude_mpm/cli/parsers/config_parser.py +153 -83
  20. claude_mpm/cli/parsers/skills_parser.py +3 -2
  21. claude_mpm/cli/startup.py +333 -89
  22. claude_mpm/commands/mpm-config.md +266 -0
  23. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  24. claude_mpm/config/agent_sources.py +27 -0
  25. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  26. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  27. claude_mpm/core/framework_loader.py +4 -2
  28. claude_mpm/core/logger.py +13 -0
  29. claude_mpm/core/socketio_pool.py +3 -3
  30. claude_mpm/core/unified_agent_registry.py +5 -15
  31. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  32. claude_mpm/hooks/claude_hooks/event_handlers.py +206 -78
  33. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  34. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  35. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  36. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  37. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  38. claude_mpm/hooks/memory_integration_hook.py +46 -1
  39. claude_mpm/init.py +0 -19
  40. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  41. claude_mpm/scripts/launch_monitor.py +93 -13
  42. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  43. claude_mpm/services/agents/agent_review_service.py +280 -0
  44. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  45. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  46. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  47. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
  48. claude_mpm/services/agents/git_source_manager.py +34 -0
  49. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  51. claude_mpm/services/agents/toolchain_detector.py +10 -6
  52. claude_mpm/services/analysis/__init__.py +11 -1
  53. claude_mpm/services/analysis/clone_detector.py +1030 -0
  54. claude_mpm/services/command_deployment_service.py +71 -10
  55. claude_mpm/services/event_bus/config.py +3 -1
  56. claude_mpm/services/git/git_operations_service.py +93 -8
  57. claude_mpm/services/monitor/daemon.py +9 -2
  58. claude_mpm/services/monitor/daemon_manager.py +39 -3
  59. claude_mpm/services/monitor/server.py +225 -19
  60. claude_mpm/services/self_upgrade_service.py +120 -12
  61. claude_mpm/services/skills/__init__.py +3 -0
  62. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  63. claude_mpm/services/skills/selective_skill_deployer.py +230 -0
  64. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  65. claude_mpm/services/skills_deployer.py +64 -3
  66. claude_mpm/services/socketio/event_normalizer.py +15 -1
  67. claude_mpm/services/socketio/server/core.py +160 -21
  68. claude_mpm/services/version_control/git_operations.py +103 -0
  69. claude_mpm/utils/agent_filters.py +17 -44
  70. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/METADATA +47 -84
  71. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/RECORD +76 -150
  72. claude_mpm-5.4.14.dist-info/entry_points.txt +5 -0
  73. claude_mpm-5.4.14.dist-info/licenses/LICENSE +94 -0
  74. claude_mpm-5.4.14.dist-info/licenses/LICENSE-FAQ.md +153 -0
  75. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  76. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  77. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  78. claude_mpm/agents/BASE_OPS.md +0 -219
  79. claude_mpm/agents/BASE_PM.md +0 -480
  80. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  81. claude_mpm/agents/BASE_QA.md +0 -167
  82. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  83. claude_mpm/agents/base_agent.json +0 -31
  84. claude_mpm/agents/base_agent_loader.py +0 -601
  85. claude_mpm/cli/ticket_cli.py +0 -35
  86. claude_mpm/commands/mpm-config-view.md +0 -150
  87. claude_mpm/dashboard/analysis_runner.py +0 -455
  88. claude_mpm/dashboard/index.html +0 -13
  89. claude_mpm/dashboard/open_dashboard.py +0 -66
  90. claude_mpm/dashboard/static/css/activity.css +0 -1958
  91. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  92. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  93. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  94. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  95. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  96. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  97. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  98. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  99. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  100. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  101. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  102. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  103. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  104. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  105. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  106. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  107. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  108. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  109. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  110. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  111. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  112. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  113. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  114. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  115. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  116. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  117. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  118. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  119. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  120. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  121. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  122. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  123. claude_mpm/dashboard/templates/code_simple.html +0 -153
  124. claude_mpm/dashboard/templates/index.html +0 -606
  125. claude_mpm/dashboard/test_dashboard.html +0 -372
  126. claude_mpm/scripts/mcp_server.py +0 -75
  127. claude_mpm/scripts/mcp_wrapper.py +0 -39
  128. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  129. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  130. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  131. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  132. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  133. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  134. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  135. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  136. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  137. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  138. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  139. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  140. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  141. claude_mpm/services/mcp_gateway/main.py +0 -589
  142. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  143. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  144. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  145. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  146. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  147. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  148. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  149. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  150. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  151. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  152. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  153. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  154. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  155. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  156. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  157. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  158. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  159. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  160. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  161. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/WHEEL +0 -0
  162. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,280 @@
1
+ """Agent review service for comparing project agents with managed agents.
2
+
3
+ WHY: This service helps users maintain a clean agent directory by:
4
+ 1. Identifying which agents are managed vs custom
5
+ 2. Detecting outdated versions of managed agents
6
+ 3. Finding unused agents that don't match the detected toolchain
7
+ 4. Safely archiving unnecessary agents instead of deleting them
8
+
9
+ DESIGN DECISIONS:
10
+ - Archive to .claude/agents/unused/ instead of deleting (safe, recoverable)
11
+ - Add timestamps to archived files to prevent conflicts
12
+ - Preserve custom user agents (not in managed set)
13
+ - Compare versions to detect outdated managed agents
14
+ """
15
+
16
+ import shutil
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Set
20
+
21
+ from claude_mpm.core.logging_config import get_logger
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class AgentReviewService:
27
+ """Service for reviewing and managing project agents.
28
+
29
+ This service analyzes the relationship between project agents and managed
30
+ agents from the claude-mpm-agents repository, categorizing them as:
31
+ - Managed: In sync with managed agents
32
+ - Outdated: Older version of managed agent exists
33
+ - Custom: User-created agents not in managed set
34
+ - Unused: Not recommended for this project's toolchain
35
+ """
36
+
37
+ def __init__(self):
38
+ """Initialize the agent review service."""
39
+ self.logger = get_logger(__name__)
40
+
41
+ def review_project_agents(
42
+ self,
43
+ project_agents_dir: Path,
44
+ managed_agents: List[Dict[str, Any]],
45
+ recommended_agent_ids: Set[str],
46
+ ) -> Dict[str, List[Dict[str, Any]]]:
47
+ """Review existing project agents and categorize them.
48
+
49
+ Args:
50
+ project_agents_dir: Directory containing project agents (.claude/agents/)
51
+ managed_agents: List of managed agent dicts from cache
52
+ recommended_agent_ids: Set of agent IDs recommended for this toolchain
53
+
54
+ Returns:
55
+ Dictionary with categorized agents:
56
+ {
57
+ "managed": [...], # In sync with managed
58
+ "outdated": [...], # Older version exists
59
+ "custom": [...], # User-created
60
+ "unused": [...], # Not needed for this toolchain
61
+ }
62
+ """
63
+ results = {
64
+ "managed": [],
65
+ "outdated": [],
66
+ "custom": [],
67
+ "unused": [],
68
+ }
69
+
70
+ if not project_agents_dir.exists():
71
+ self.logger.debug(
72
+ f"Project agents directory does not exist: {project_agents_dir}"
73
+ )
74
+ return results
75
+
76
+ # Build lookup map of managed agents by ID
77
+ managed_by_id = {agent["agent_id"]: agent for agent in managed_agents}
78
+
79
+ # Scan project agents
80
+ for agent_file in project_agents_dir.glob("*.md"):
81
+ # Skip the unused directory itself
82
+ if agent_file.name == "unused":
83
+ continue
84
+
85
+ agent_name = agent_file.stem
86
+
87
+ # Parse agent to get version and metadata
88
+ project_agent_info = self._parse_project_agent(agent_file)
89
+
90
+ # Check if this is a managed agent
91
+ if agent_name in managed_by_id:
92
+ managed_agent = managed_by_id[agent_name]
93
+
94
+ # Compare versions
95
+ project_version = project_agent_info.get("version", "unknown")
96
+ managed_version = managed_agent.get("version", "unknown")
97
+
98
+ if self._is_outdated(project_version, managed_version):
99
+ # Outdated version of managed agent
100
+ results["outdated"].append(
101
+ {
102
+ "name": agent_name,
103
+ "path": agent_file,
104
+ "current_version": project_version,
105
+ "available_version": managed_version,
106
+ "recommended": agent_name in recommended_agent_ids,
107
+ }
108
+ )
109
+ else:
110
+ # Up-to-date managed agent
111
+ results["managed"].append(
112
+ {
113
+ "name": agent_name,
114
+ "path": agent_file,
115
+ "version": project_version,
116
+ "recommended": agent_name in recommended_agent_ids,
117
+ }
118
+ )
119
+ else:
120
+ # Custom user agent (not in managed set)
121
+ results["custom"].append(
122
+ {
123
+ "name": agent_name,
124
+ "path": agent_file,
125
+ "version": project_agent_info.get("version", "unknown"),
126
+ }
127
+ )
128
+
129
+ # Identify unused agents (managed or outdated but not recommended)
130
+ for category in ["managed", "outdated"]:
131
+ for agent in results[category][:]: # Copy list to modify during iteration
132
+ if not agent.get("recommended", False):
133
+ # This managed/outdated agent is not recommended for this toolchain
134
+ results["unused"].append(agent)
135
+ results[category].remove(agent)
136
+
137
+ self.logger.info(
138
+ f"Agent review complete: "
139
+ f"{len(results['managed'])} managed, "
140
+ f"{len(results['outdated'])} outdated, "
141
+ f"{len(results['custom'])} custom, "
142
+ f"{len(results['unused'])} unused"
143
+ )
144
+
145
+ return results
146
+
147
+ def archive_agents(
148
+ self, agents_to_archive: List[Dict[str, Any]], project_agents_dir: Path
149
+ ) -> Dict[str, Any]:
150
+ """Archive agents by moving them to .claude/agents/unused/.
151
+
152
+ Args:
153
+ agents_to_archive: List of agent dicts with 'name' and 'path' keys
154
+ project_agents_dir: Base agents directory (.claude/agents/)
155
+
156
+ Returns:
157
+ Dictionary with archival results:
158
+ {
159
+ "archived": [...], # Successfully archived
160
+ "errors": [...], # Archival errors
161
+ }
162
+ """
163
+ results = {"archived": [], "errors": []}
164
+
165
+ if not agents_to_archive:
166
+ return results
167
+
168
+ # Create unused directory
169
+ unused_dir = project_agents_dir / "unused"
170
+ unused_dir.mkdir(exist_ok=True)
171
+
172
+ for agent in agents_to_archive:
173
+ agent_path = agent["path"]
174
+ agent_name = agent["name"]
175
+
176
+ try:
177
+ # Generate timestamped filename to avoid conflicts
178
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
179
+ archived_name = f"{agent_name}_{timestamp}.md"
180
+ archived_path = unused_dir / archived_name
181
+
182
+ # Move the file
183
+ shutil.move(str(agent_path), str(archived_path))
184
+
185
+ results["archived"].append(
186
+ {
187
+ "name": agent_name,
188
+ "original_path": str(agent_path),
189
+ "archived_path": str(archived_path),
190
+ }
191
+ )
192
+
193
+ self.logger.debug(f"Archived {agent_name} to {archived_path}")
194
+
195
+ except Exception as e:
196
+ error_msg = f"Failed to archive {agent_name}: {e}"
197
+ self.logger.error(error_msg)
198
+ results["errors"].append(error_msg)
199
+
200
+ self.logger.info(
201
+ f"Archived {len(results['archived'])} agents, "
202
+ f"{len(results['errors'])} errors"
203
+ )
204
+
205
+ return results
206
+
207
+ def _parse_project_agent(self, agent_file: Path) -> Dict[str, Any]:
208
+ """Parse a project agent file to extract metadata.
209
+
210
+ Args:
211
+ agent_file: Path to agent Markdown file
212
+
213
+ Returns:
214
+ Dictionary with agent metadata (version, name, etc.)
215
+ """
216
+ try:
217
+ content = agent_file.read_text(encoding="utf-8")
218
+
219
+ # Extract version from YAML frontmatter
220
+ import re
221
+
222
+ version_match = re.search(
223
+ r'^version:\s*["\']?(.+?)["\']?$', content, re.MULTILINE
224
+ )
225
+ version = version_match.group(1) if version_match else "unknown"
226
+
227
+ return {
228
+ "version": version,
229
+ "name": agent_file.stem,
230
+ }
231
+
232
+ except Exception as e:
233
+ self.logger.warning(f"Failed to parse agent {agent_file.name}: {e}")
234
+ return {"version": "unknown", "name": agent_file.stem}
235
+
236
+ def _is_outdated(self, current_version: str, available_version: str) -> bool:
237
+ """Check if current version is outdated compared to available version.
238
+
239
+ Args:
240
+ current_version: Currently deployed version
241
+ available_version: Available version from managed agents
242
+
243
+ Returns:
244
+ True if current version is outdated
245
+ """
246
+ # Handle unknown versions
247
+ if current_version == "unknown" or available_version == "unknown":
248
+ return False
249
+
250
+ # Simple string comparison for now
251
+ # TODO: Implement semantic version comparison (1.2.3 vs 1.2.4)
252
+ return current_version != available_version
253
+
254
+ def get_archive_summary(self, project_agents_dir: Path) -> Dict[str, Any]:
255
+ """Get summary of archived agents.
256
+
257
+ Args:
258
+ project_agents_dir: Base agents directory (.claude/agents/)
259
+
260
+ Returns:
261
+ Dictionary with archive statistics
262
+ """
263
+ unused_dir = project_agents_dir / "unused"
264
+
265
+ if not unused_dir.exists():
266
+ return {"count": 0, "agents": []}
267
+
268
+ archived_files = list(unused_dir.glob("*.md"))
269
+
270
+ return {
271
+ "count": len(archived_files),
272
+ "agents": [
273
+ {
274
+ "name": f.stem,
275
+ "path": str(f),
276
+ "size_bytes": f.stat().st_size,
277
+ }
278
+ for f in archived_files
279
+ ],
280
+ }
@@ -215,9 +215,8 @@ class AgentDiscoveryService:
215
215
  # Extract YAML frontmatter
216
216
  frontmatter = self._extract_yaml_frontmatter(template_content)
217
217
  if not frontmatter:
218
- self.logger.warning(
219
- f"No valid YAML frontmatter in {template_file.name}"
220
- )
218
+ # Silently return None for files without frontmatter
219
+ # (e.g., PM instruction templates in templates/ directory)
221
220
  return None
222
221
 
223
222
  # Extract metadata directly from frontmatter (flat structure)
@@ -129,12 +129,13 @@ class AgentTemplateBuilder:
129
129
  base_templates.append(base_agent_file)
130
130
  self.logger.debug(f"Found BASE-AGENT.md at: {base_agent_file}")
131
131
 
132
- # Stop at git repository root if detected
132
+ # Stop at git repository root if detected (check AFTER finding BASE-AGENT.md)
133
133
  if (current_dir / ".git").exists():
134
134
  self.logger.debug(f"Reached git repository root at: {current_dir}")
135
135
  break
136
136
 
137
- # Stop at common repository root indicators
137
+ # Stop at common repository root indicators (check AFTER finding BASE-AGENT.md)
138
+ # This ensures we check the 'agents' directory before stopping at 'remote-agents'
138
139
  if current_dir.name in [".claude-mpm", "remote-agents", "cache"]:
139
140
  self.logger.debug(
140
141
  f"Reached repository root indicator at: {current_dir}"
@@ -675,6 +676,7 @@ Only include memories that are:
675
676
  """
676
677
  content = content + memory_instructions
677
678
 
679
+ # Combine frontmatter and content
678
680
  return frontmatter + content
679
681
 
680
682
  def build_agent_yaml(
@@ -16,6 +16,8 @@ import os
16
16
  from pathlib import Path
17
17
  from typing import Any, Dict, List, Optional, Tuple
18
18
 
19
+ import yaml
20
+
19
21
  from claude_mpm.core.config import Config
20
22
  from claude_mpm.core.logging_config import get_logger
21
23
 
@@ -51,6 +53,70 @@ class MultiSourceAgentDeploymentService:
51
53
  self.logger = get_logger(__name__)
52
54
  self.version_manager = AgentVersionManager()
53
55
 
56
+ def _read_template_version(self, template_path: Path) -> Optional[str]:
57
+ """Read version from template file (supports both .md and .json formats).
58
+
59
+ For .md files: Extract version from YAML frontmatter
60
+ For .json files: Extract version from JSON structure
61
+
62
+ Args:
63
+ template_path: Path to template file
64
+
65
+ Returns:
66
+ Version string or None if version cannot be extracted
67
+ """
68
+ try:
69
+ if template_path.suffix == ".md":
70
+ # Parse markdown with YAML frontmatter
71
+ content = template_path.read_text()
72
+
73
+ # Extract YAML frontmatter (between --- markers)
74
+ if not content.strip().startswith("---"):
75
+ return None
76
+
77
+ parts = content.split("---", 2)
78
+ if len(parts) < 3:
79
+ return None
80
+
81
+ # Parse YAML frontmatter
82
+ frontmatter = yaml.safe_load(parts[1])
83
+ if not frontmatter:
84
+ return None
85
+
86
+ # Extract version from frontmatter
87
+ version = frontmatter.get("version")
88
+ return version if version else None
89
+
90
+ if template_path.suffix == ".json":
91
+ # Parse JSON template
92
+ template_data = json.loads(template_path.read_text())
93
+ metadata = template_data.get("metadata", {})
94
+ version = (
95
+ template_data.get("agent_version")
96
+ or template_data.get("version")
97
+ or metadata.get("version")
98
+ )
99
+ return version if version else None
100
+
101
+ self.logger.warning(
102
+ f"Unknown template format: {template_path.suffix} for {template_path.name}"
103
+ )
104
+ return None
105
+
106
+ except yaml.YAMLError as e:
107
+ self.logger.warning(
108
+ f"Invalid YAML frontmatter in {template_path.name}: {e}"
109
+ )
110
+ return None
111
+ except json.JSONDecodeError as e:
112
+ self.logger.warning(f"Invalid JSON in {template_path.name}: {e}")
113
+ return None
114
+ except Exception as e:
115
+ self.logger.warning(
116
+ f"Error reading template version from {template_path.name}: {e}"
117
+ )
118
+ return None
119
+
54
120
  def _build_canonical_id_for_agent(self, agent_info: Dict[str, Any]) -> str:
55
121
  """Build or retrieve canonical_id for an agent.
56
122
 
@@ -827,17 +893,20 @@ class MultiSourceAgentDeploymentService:
827
893
  comparison_results["needs_update"].append(agent_name)
828
894
  continue
829
895
 
830
- # Read template version
831
- try:
832
- template_data = json.loads(template_path.read_text())
833
- metadata = template_data.get("metadata", {})
834
- template_version = self.version_manager.parse_version(
835
- template_data.get("agent_version")
836
- or template_data.get("version")
837
- or metadata.get("version", "0.0.0")
896
+ # Read template version using format-aware helper
897
+ version_string = self._read_template_version(template_path)
898
+ if not version_string:
899
+ self.logger.warning(
900
+ f"Could not extract version from template for '{agent_name}', skipping"
838
901
  )
902
+ continue
903
+
904
+ try:
905
+ template_version = self.version_manager.parse_version(version_string)
839
906
  except Exception as e:
840
- self.logger.warning(f"Error reading template for '{agent_name}': {e}")
907
+ self.logger.warning(
908
+ f"Error parsing version '{version_string}' for '{agent_name}': {e}"
909
+ )
841
910
  continue
842
911
 
843
912
  # Read deployed version