claude-mpm 3.5.4__py3-none-any.whl → 3.6.0__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.
Files changed (68) hide show
  1. claude_mpm/.claude-mpm/logs/hooks_20250728.log +10 -0
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +96 -23
  4. claude_mpm/agents/BASE_PM.md +273 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +114 -102
  6. claude_mpm/agents/agent-template.yaml +83 -0
  7. claude_mpm/agents/agent_loader.py +36 -1
  8. claude_mpm/agents/async_agent_loader.py +421 -0
  9. claude_mpm/agents/templates/code_analyzer.json +81 -0
  10. claude_mpm/agents/templates/data_engineer.json +18 -3
  11. claude_mpm/agents/templates/documentation.json +18 -3
  12. claude_mpm/agents/templates/engineer.json +19 -4
  13. claude_mpm/agents/templates/ops.json +18 -3
  14. claude_mpm/agents/templates/qa.json +20 -4
  15. claude_mpm/agents/templates/research.json +20 -4
  16. claude_mpm/agents/templates/security.json +18 -3
  17. claude_mpm/agents/templates/version_control.json +16 -3
  18. claude_mpm/cli/README.md +108 -0
  19. claude_mpm/cli/__init__.py +5 -1
  20. claude_mpm/cli/commands/__init__.py +5 -1
  21. claude_mpm/cli/commands/agents.py +233 -6
  22. claude_mpm/cli/commands/aggregate.py +462 -0
  23. claude_mpm/cli/commands/config.py +277 -0
  24. claude_mpm/cli/commands/run.py +228 -47
  25. claude_mpm/cli/parser.py +176 -1
  26. claude_mpm/cli/utils.py +9 -1
  27. claude_mpm/cli_module/refactoring_guide.md +253 -0
  28. claude_mpm/config/async_logging_config.yaml +145 -0
  29. claude_mpm/constants.py +19 -0
  30. claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +34 -0
  31. claude_mpm/core/claude_runner.py +413 -76
  32. claude_mpm/core/config.py +161 -4
  33. claude_mpm/core/config_paths.py +0 -1
  34. claude_mpm/core/factories.py +9 -3
  35. claude_mpm/core/framework_loader.py +81 -0
  36. claude_mpm/dashboard/.claude-mpm/memories/README.md +36 -0
  37. claude_mpm/dashboard/README.md +121 -0
  38. claude_mpm/dashboard/static/js/dashboard.js.backup +1973 -0
  39. claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +36 -0
  40. claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +39 -0
  41. claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +38 -0
  42. claude_mpm/hooks/README.md +96 -0
  43. claude_mpm/hooks/claude_hooks/hook_handler.py +391 -9
  44. claude_mpm/init.py +123 -18
  45. claude_mpm/models/agent_session.py +511 -0
  46. claude_mpm/schemas/agent_schema.json +435 -0
  47. claude_mpm/scripts/__init__.py +15 -0
  48. claude_mpm/scripts/start_activity_logging.py +86 -0
  49. claude_mpm/services/agents/deployment/agent_deployment.py +326 -24
  50. claude_mpm/services/agents/deployment/async_agent_deployment.py +461 -0
  51. claude_mpm/services/agents/management/agent_management_service.py +2 -1
  52. claude_mpm/services/event_aggregator.py +547 -0
  53. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  54. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +3 -3
  55. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +2 -2
  56. claude_mpm/services/version_control/VERSION +1 -0
  57. claude_mpm/utils/agent_dependency_loader.py +655 -0
  58. claude_mpm/utils/console.py +11 -0
  59. claude_mpm/utils/dependency_cache.py +376 -0
  60. claude_mpm/utils/dependency_strategies.py +343 -0
  61. claude_mpm/utils/environment_context.py +310 -0
  62. {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/METADATA +87 -1
  63. {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/RECORD +67 -37
  64. claude_mpm/agents/templates/pm.json +0 -122
  65. {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/WHEEL +0 -0
  66. {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/entry_points.txt +0 -0
  67. {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/licenses/LICENSE +0 -0
  68. {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/top_level.txt +0 -0
@@ -37,6 +37,7 @@ from typing import Optional, List, Dict, Any
37
37
  from claude_mpm.core.logger import get_logger
38
38
  from claude_mpm.constants import EnvironmentVars, Paths, AgentMetadata
39
39
  from claude_mpm.config.paths import paths
40
+ from claude_mpm.core.config import Config
40
41
 
41
42
 
42
43
  class AgentDeploymentService:
@@ -70,13 +71,14 @@ class AgentDeploymentService:
70
71
  - YAML generation capabilities
71
72
  """
72
73
 
73
- def __init__(self, templates_dir: Optional[Path] = None, base_agent_path: Optional[Path] = None):
74
+ def __init__(self, templates_dir: Optional[Path] = None, base_agent_path: Optional[Path] = None, working_directory: Optional[Path] = None):
74
75
  """
75
76
  Initialize agent deployment service.
76
77
 
77
78
  Args:
78
79
  templates_dir: Directory containing agent JSON files
79
80
  base_agent_path: Path to base_agent.md file
81
+ working_directory: User's working directory (for project agents)
80
82
 
81
83
  METRICS OPPORTUNITY: Track initialization performance:
82
84
  - Template directory scan time
@@ -100,6 +102,17 @@ class AgentDeploymentService:
100
102
  'deployment_errors': {} # Track error types and frequencies
101
103
  }
102
104
 
105
+ # Determine the actual working directory
106
+ # Priority: working_directory param > CLAUDE_MPM_USER_PWD env var > current directory
107
+ if working_directory:
108
+ self.working_directory = Path(working_directory)
109
+ elif 'CLAUDE_MPM_USER_PWD' in os.environ:
110
+ self.working_directory = Path(os.environ['CLAUDE_MPM_USER_PWD'])
111
+ else:
112
+ self.working_directory = Path.cwd()
113
+
114
+ self.logger.info(f"Working directory for deployment: {self.working_directory}")
115
+
103
116
  # Find templates directory using centralized path management
104
117
  if templates_dir:
105
118
  self.templates_dir = Path(templates_dir)
@@ -119,7 +132,7 @@ class AgentDeploymentService:
119
132
  self.logger.info(f"Templates directory: {self.templates_dir}")
120
133
  self.logger.info(f"Base agent path: {self.base_agent_path}")
121
134
 
122
- def deploy_agents(self, target_dir: Optional[Path] = None, force_rebuild: bool = False, deployment_mode: str = "update") -> Dict[str, Any]:
135
+ def deploy_agents(self, target_dir: Optional[Path] = None, force_rebuild: bool = False, deployment_mode: str = "update", config: Optional[Config] = None, use_async: bool = True) -> Dict[str, Any]:
123
136
  """
124
137
  Build and deploy agents by combining base_agent.md with templates.
125
138
  Also deploys system instructions for PM framework.
@@ -128,6 +141,12 @@ class AgentDeploymentService:
128
141
  - "update": Normal update mode - skip agents with matching versions (default)
129
142
  - "project": Project deployment mode - always deploy all agents regardless of version
130
143
 
144
+ CONFIGURATION:
145
+ The config parameter or default configuration is used to determine:
146
+ - Which agents to exclude from deployment
147
+ - Case sensitivity for agent name matching
148
+ - Whether to exclude agent dependencies
149
+
131
150
  METRICS COLLECTED:
132
151
  - Deployment start/end timestamps
133
152
  - Individual agent deployment durations
@@ -137,6 +156,7 @@ class AgentDeploymentService:
137
156
  - Error type frequencies
138
157
 
139
158
  OPERATIONAL FLOW:
159
+ 0. Validates and repairs broken frontmatter in existing agents (Step 0)
140
160
  1. Validates target directory (creates if needed)
141
161
  2. Loads base agent configuration
142
162
  3. Discovers all agent templates
@@ -168,6 +188,8 @@ class AgentDeploymentService:
168
188
  target_dir: Target directory for agents (default: .claude/agents/)
169
189
  force_rebuild: Force rebuild even if agents exist (useful for troubleshooting)
170
190
  deployment_mode: "update" for version-aware updates, "project" for always deploy
191
+ config: Optional configuration object (loads default if not provided)
192
+ use_async: Use async operations for 50-70% faster deployment (default: True)
171
193
 
172
194
  Returns:
173
195
  Dictionary with deployment results:
@@ -178,13 +200,73 @@ class AgentDeploymentService:
178
200
  - skipped: List of unchanged agents
179
201
  - errors: List of deployment errors
180
202
  - total: Total number of agents processed
203
+ - repaired: List of agents with repaired frontmatter
181
204
  """
182
205
  # METRICS: Record deployment start time for performance tracking
183
206
  deployment_start_time = time.time()
184
207
 
208
+ # Try async deployment for better performance if requested
209
+ if use_async:
210
+ try:
211
+ from .async_agent_deployment import deploy_agents_async_wrapper
212
+ self.logger.info("Using async deployment for improved performance")
213
+
214
+ # Run async deployment
215
+ results = deploy_agents_async_wrapper(
216
+ templates_dir=self.templates_dir,
217
+ base_agent_path=self.base_agent_path,
218
+ working_directory=self.working_directory,
219
+ target_dir=target_dir,
220
+ force_rebuild=force_rebuild,
221
+ config=config
222
+ )
223
+
224
+ # Add metrics about async vs sync
225
+ if 'metrics' in results:
226
+ results['metrics']['deployment_method'] = 'async'
227
+ duration_ms = results['metrics'].get('duration_ms', 0)
228
+ self.logger.info(f"Async deployment completed in {duration_ms:.1f}ms")
229
+
230
+ # Update internal metrics
231
+ self._deployment_metrics['total_deployments'] += 1
232
+ if not results.get('errors'):
233
+ self._deployment_metrics['successful_deployments'] += 1
234
+ else:
235
+ self._deployment_metrics['failed_deployments'] += 1
236
+
237
+ return results
238
+
239
+ except ImportError:
240
+ self.logger.warning("Async deployment not available, falling back to sync")
241
+ except Exception as e:
242
+ self.logger.warning(f"Async deployment failed, falling back to sync: {e}")
243
+
244
+ # Continue with synchronous deployment
245
+ self.logger.info("Using synchronous deployment")
246
+
247
+ # Load configuration if not provided
248
+ if config is None:
249
+ config = Config()
250
+
251
+ # Get agent exclusion configuration
252
+ excluded_agents = config.get('agent_deployment.excluded_agents', [])
253
+ case_sensitive = config.get('agent_deployment.case_sensitive', False)
254
+ exclude_dependencies = config.get('agent_deployment.exclude_dependencies', False)
255
+
256
+ # Normalize excluded agents list for comparison
257
+ if not case_sensitive:
258
+ excluded_agents = [agent.lower() for agent in excluded_agents]
259
+
260
+ # Log exclusion configuration if agents are being excluded
261
+ if excluded_agents:
262
+ self.logger.info(f"Excluding agents from deployment: {excluded_agents}")
263
+ self.logger.debug(f"Case sensitive matching: {case_sensitive}")
264
+ self.logger.debug(f"Exclude dependencies: {exclude_dependencies}")
265
+
185
266
  if not target_dir:
186
- # Default to user's home .claude/agents directory
187
- agents_dir = Path(Paths.CLAUDE_AGENTS_DIR.value).expanduser()
267
+ # Default to working directory's .claude/agents directory (not home)
268
+ # This ensures we deploy to the user's project directory, not the framework directory
269
+ agents_dir = self.working_directory / ".claude" / "agents"
188
270
  else:
189
271
  # If target_dir provided, use it directly (caller decides structure)
190
272
  # This allows for both passing a project dir or the full agents path
@@ -211,6 +293,7 @@ class AgentDeploymentService:
211
293
  "updated": [],
212
294
  "migrated": [], # Track agents migrated from old format
213
295
  "converted": [], # Track YAML to MD conversions
296
+ "repaired": [], # Track agents with repaired frontmatter
214
297
  "total": 0,
215
298
  # METRICS: Add detailed timing and performance data to results
216
299
  "metrics": {
@@ -226,6 +309,16 @@ class AgentDeploymentService:
226
309
  try:
227
310
  # Create agents directory if needed
228
311
  agents_dir.mkdir(parents=True, exist_ok=True)
312
+
313
+ # STEP 0: Validate and repair broken frontmatter in existing agents
314
+ # This ensures all existing agents have valid YAML frontmatter before deploying new ones
315
+ repair_results = self._validate_and_repair_existing_agents(agents_dir)
316
+ if repair_results["repaired"]:
317
+ results["repaired"] = repair_results["repaired"]
318
+ self.logger.info(f"Repaired frontmatter in {len(repair_results['repaired'])} existing agents")
319
+ for agent_name in repair_results["repaired"]:
320
+ self.logger.debug(f" - Repaired: {agent_name}")
321
+
229
322
  # Determine source tier for logging
230
323
  source_tier = "SYSTEM"
231
324
  if ".claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
@@ -268,16 +361,54 @@ class AgentDeploymentService:
268
361
 
269
362
  # Get all template files
270
363
  template_files = list(self.templates_dir.glob("*.json"))
271
- # Filter out non-agent files - exclude system files and uppercase special files
272
- excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README"}
273
- template_files = [
274
- f for f in template_files
275
- if f.stem not in excluded_names
276
- and not f.stem.startswith(".")
277
- and not f.stem.endswith(".backup")
278
- ]
364
+
365
+ # Build the combined exclusion set
366
+ # Start with hardcoded exclusions (these are ALWAYS excluded)
367
+ hardcoded_exclusions = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
368
+
369
+ # Add user-configured exclusions
370
+ user_exclusions = set()
371
+ for agent in excluded_agents:
372
+ if case_sensitive:
373
+ user_exclusions.add(agent)
374
+ else:
375
+ # For case-insensitive, we'll check during filtering
376
+ user_exclusions.add(agent.lower())
377
+
378
+ # Filter out excluded agents
379
+ filtered_files = []
380
+ excluded_count = 0
381
+ for f in template_files:
382
+ agent_name = f.stem
383
+
384
+ # Check hardcoded exclusions (always case-sensitive)
385
+ if agent_name in hardcoded_exclusions:
386
+ self.logger.debug(f"Excluding {agent_name}: hardcoded system exclusion")
387
+ excluded_count += 1
388
+ continue
389
+
390
+ # Check file patterns
391
+ if agent_name.startswith(".") or agent_name.endswith(".backup"):
392
+ self.logger.debug(f"Excluding {agent_name}: file pattern exclusion")
393
+ excluded_count += 1
394
+ continue
395
+
396
+ # Check user-configured exclusions
397
+ compare_name = agent_name.lower() if not case_sensitive else agent_name
398
+ if compare_name in user_exclusions:
399
+ self.logger.info(f"Excluding {agent_name}: user-configured exclusion")
400
+ excluded_count += 1
401
+ continue
402
+
403
+ # Agent is not excluded, add to filtered list
404
+ filtered_files.append(f)
405
+
406
+ template_files = filtered_files
279
407
  results["total"] = len(template_files)
280
408
 
409
+ if excluded_count > 0:
410
+ self.logger.info(f"Excluded {excluded_count} agents from deployment")
411
+
281
412
  for template_file in template_files:
282
413
  try:
283
414
  # METRICS: Track individual agent deployment time
@@ -373,6 +504,7 @@ class AgentDeploymentService:
373
504
  f"updated {len(results['updated'])}, "
374
505
  f"migrated {len(results['migrated'])}, "
375
506
  f"converted {len(results['converted'])} YAML files, "
507
+ f"repaired {len(results['repaired'])} frontmatter, "
376
508
  f"skipped {len(results['skipped'])}, "
377
509
  f"errors: {len(results['errors'])}"
378
510
  )
@@ -864,7 +996,8 @@ temperature: {temperature}"""
864
996
  Dictionary of environment variables set for verification
865
997
  """
866
998
  if not config_dir:
867
- config_dir = Path.cwd() / Paths.CLAUDE_CONFIG_DIR.value
999
+ # Use the working directory determined during initialization
1000
+ config_dir = self.working_directory / Paths.CLAUDE_CONFIG_DIR.value
868
1001
 
869
1002
  env_vars = {}
870
1003
 
@@ -930,7 +1063,8 @@ temperature: {temperature}"""
930
1063
  - warnings: List of potential issues
931
1064
  """
932
1065
  if not config_dir:
933
- config_dir = Path.cwd() / ".claude"
1066
+ # Use the working directory determined during initialization
1067
+ config_dir = self.working_directory / ".claude"
934
1068
 
935
1069
  results = {
936
1070
  "config_dir": str(config_dir),
@@ -953,6 +1087,17 @@ temperature: {temperature}"""
953
1087
 
954
1088
  # List deployed agents
955
1089
  agent_files = list(agents_dir.glob("*.md"))
1090
+
1091
+ # Get exclusion configuration for logging purposes
1092
+ try:
1093
+ from claude_mpm.core.config import Config
1094
+ config = Config()
1095
+ excluded_agents = config.get('agent_deployment.excluded_agents', [])
1096
+ if excluded_agents:
1097
+ self.logger.debug(f"Note: The following agents are configured for exclusion: {excluded_agents}")
1098
+ except Exception:
1099
+ pass # Ignore config loading errors in verification
1100
+
956
1101
  for agent_file in agent_files:
957
1102
  try:
958
1103
  # Read first few lines to get agent name from YAML
@@ -1093,14 +1238,46 @@ temperature: {temperature}"""
1093
1238
  return agents
1094
1239
 
1095
1240
  template_files = sorted(self.templates_dir.glob("*.json"))
1096
- # Filter out non-agent files - exclude system files and uppercase special files
1097
- excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README"}
1098
- template_files = [
1099
- f for f in template_files
1100
- if f.stem not in excluded_names
1101
- and not f.stem.startswith(".")
1102
- and not f.stem.endswith(".backup")
1103
- ]
1241
+
1242
+ # Load configuration for exclusions
1243
+ try:
1244
+ from claude_mpm.core.config import Config
1245
+ config = Config()
1246
+ excluded_agents = config.get('agent_deployment.excluded_agents', [])
1247
+ case_sensitive = config.get('agent_deployment.case_sensitive', False)
1248
+
1249
+ # Normalize excluded agents for comparison
1250
+ if not case_sensitive:
1251
+ excluded_agents = [agent.lower() for agent in excluded_agents]
1252
+ except Exception:
1253
+ # If config loading fails, use empty exclusion list
1254
+ excluded_agents = []
1255
+ case_sensitive = False
1256
+
1257
+ # Build combined exclusion set
1258
+ hardcoded_exclusions = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
1259
+
1260
+ # Filter out excluded agents
1261
+ filtered_files = []
1262
+ for f in template_files:
1263
+ agent_name = f.stem
1264
+
1265
+ # Check hardcoded exclusions
1266
+ if agent_name in hardcoded_exclusions:
1267
+ continue
1268
+
1269
+ # Check file patterns
1270
+ if agent_name.startswith(".") or agent_name.endswith(".backup"):
1271
+ continue
1272
+
1273
+ # Check user-configured exclusions
1274
+ compare_name = agent_name.lower() if not case_sensitive else agent_name
1275
+ if compare_name in excluded_agents:
1276
+ continue
1277
+
1278
+ filtered_files.append(f)
1279
+
1280
+ template_files = filtered_files
1104
1281
 
1105
1282
  for template_file in template_files:
1106
1283
  try:
@@ -1283,7 +1460,8 @@ temperature: {temperature}"""
1283
1460
  Cleanup results
1284
1461
  """
1285
1462
  if not config_dir:
1286
- config_dir = Path.cwd() / ".claude"
1463
+ # Use the working directory determined during initialization
1464
+ config_dir = self.working_directory / ".claude"
1287
1465
 
1288
1466
  results = {
1289
1467
  "removed": [],
@@ -1934,4 +2112,128 @@ metadata:
1934
2112
  except Exception as e:
1935
2113
  self.logger.warning(f"Error extracting YAML field '{field_name}': {e}")
1936
2114
 
1937
- return None
2115
+ return None
2116
+
2117
+ def _validate_and_repair_existing_agents(self, agents_dir: Path) -> Dict[str, Any]:
2118
+ """
2119
+ Validate and repair broken frontmatter in existing agent files.
2120
+
2121
+ This method scans existing .claude/agents/*.md files and validates their
2122
+ frontmatter. If the frontmatter is broken or missing, it attempts to repair
2123
+ it or marks the agent for replacement during deployment.
2124
+
2125
+ WHY: Ensures all existing agents have valid YAML frontmatter before deployment,
2126
+ preventing runtime errors in Claude Code when loading agents.
2127
+
2128
+ Args:
2129
+ agents_dir: Directory containing agent .md files
2130
+
2131
+ Returns:
2132
+ Dictionary with validation results:
2133
+ - repaired: List of agent names that were repaired
2134
+ - replaced: List of agent names marked for replacement
2135
+ - errors: List of validation errors
2136
+ """
2137
+ results = {
2138
+ "repaired": [],
2139
+ "replaced": [],
2140
+ "errors": []
2141
+ }
2142
+
2143
+ try:
2144
+ # Import frontmatter validator
2145
+ from claude_mpm.agents.frontmatter_validator import FrontmatterValidator
2146
+ validator = FrontmatterValidator()
2147
+
2148
+ # Find existing agent files
2149
+ agent_files = list(agents_dir.glob("*.md"))
2150
+
2151
+ if not agent_files:
2152
+ self.logger.debug("No existing agent files to validate")
2153
+ return results
2154
+
2155
+ self.logger.debug(f"Validating frontmatter in {len(agent_files)} existing agents")
2156
+
2157
+ for agent_file in agent_files:
2158
+ try:
2159
+ agent_name = agent_file.stem
2160
+
2161
+ # Read agent file content
2162
+ content = agent_file.read_text()
2163
+
2164
+ # Check if this is a system agent (authored by claude-mpm)
2165
+ # Only repair system agents, leave user agents alone
2166
+ if "author: claude-mpm" not in content and "author: 'claude-mpm'" not in content:
2167
+ self.logger.debug(f"Skipping validation for user agent: {agent_name}")
2168
+ continue
2169
+
2170
+ # Extract and validate frontmatter
2171
+ if not content.startswith("---"):
2172
+ # No frontmatter at all - mark for replacement
2173
+ self.logger.warning(f"Agent {agent_name} has no frontmatter, marking for replacement")
2174
+ results["replaced"].append(agent_name)
2175
+ # Delete the file so it will be recreated
2176
+ agent_file.unlink()
2177
+ continue
2178
+
2179
+ # Try to extract frontmatter
2180
+ try:
2181
+ end_marker = content.find("\n---\n", 4)
2182
+ if end_marker == -1:
2183
+ end_marker = content.find("\n---\r\n", 4)
2184
+
2185
+ if end_marker == -1:
2186
+ # Broken frontmatter - mark for replacement
2187
+ self.logger.warning(f"Agent {agent_name} has broken frontmatter, marking for replacement")
2188
+ results["replaced"].append(agent_name)
2189
+ # Delete the file so it will be recreated
2190
+ agent_file.unlink()
2191
+ continue
2192
+
2193
+ # Validate frontmatter with the validator
2194
+ validation_result = validator.validate_file(agent_file)
2195
+
2196
+ if not validation_result.is_valid:
2197
+ # Check if it can be corrected
2198
+ if validation_result.corrected_frontmatter:
2199
+ # Apply corrections
2200
+ correction_result = validator.correct_file(agent_file, dry_run=False)
2201
+ if correction_result.corrections:
2202
+ results["repaired"].append(agent_name)
2203
+ self.logger.info(f"Repaired frontmatter for agent {agent_name}")
2204
+ for correction in correction_result.corrections:
2205
+ self.logger.debug(f" - {correction}")
2206
+ else:
2207
+ # Cannot be corrected - mark for replacement
2208
+ self.logger.warning(f"Agent {agent_name} has invalid frontmatter that cannot be repaired, marking for replacement")
2209
+ results["replaced"].append(agent_name)
2210
+ # Delete the file so it will be recreated
2211
+ agent_file.unlink()
2212
+ elif validation_result.warnings:
2213
+ # Has warnings but is valid
2214
+ for warning in validation_result.warnings:
2215
+ self.logger.debug(f"Agent {agent_name} warning: {warning}")
2216
+
2217
+ except Exception as e:
2218
+ # Any error in parsing - mark for replacement
2219
+ self.logger.warning(f"Failed to parse frontmatter for {agent_name}: {e}, marking for replacement")
2220
+ results["replaced"].append(agent_name)
2221
+ # Delete the file so it will be recreated
2222
+ try:
2223
+ agent_file.unlink()
2224
+ except Exception:
2225
+ pass
2226
+
2227
+ except Exception as e:
2228
+ error_msg = f"Failed to validate agent {agent_file.name}: {e}"
2229
+ self.logger.error(error_msg)
2230
+ results["errors"].append(error_msg)
2231
+
2232
+ except ImportError:
2233
+ self.logger.warning("FrontmatterValidator not available, skipping validation")
2234
+ except Exception as e:
2235
+ error_msg = f"Agent validation failed: {e}"
2236
+ self.logger.error(error_msg)
2237
+ results["errors"].append(error_msg)
2238
+
2239
+ return results