claude-mpm 3.5.2__py3-none-any.whl → 3.5.6__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 (53) hide show
  1. claude_mpm/.claude-mpm/logs/hooks_20250728.log +10 -0
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/agents/INSTRUCTIONS.md +14 -13
  4. claude_mpm/agents/agent-template.yaml +83 -0
  5. claude_mpm/agents/agent_loader.py +109 -15
  6. claude_mpm/agents/base_agent.json +1 -1
  7. claude_mpm/agents/frontmatter_validator.py +448 -0
  8. claude_mpm/agents/templates/data_engineer.json +4 -3
  9. claude_mpm/agents/templates/documentation.json +4 -3
  10. claude_mpm/agents/templates/engineer.json +4 -3
  11. claude_mpm/agents/templates/ops.json +4 -3
  12. claude_mpm/agents/templates/pm.json +5 -4
  13. claude_mpm/agents/templates/qa.json +4 -3
  14. claude_mpm/agents/templates/research.json +8 -7
  15. claude_mpm/agents/templates/security.json +4 -3
  16. claude_mpm/agents/templates/test_integration.json +4 -3
  17. claude_mpm/agents/templates/version_control.json +4 -3
  18. claude_mpm/cli/README.md +108 -0
  19. claude_mpm/cli/commands/agents.py +373 -7
  20. claude_mpm/cli/commands/run.py +4 -11
  21. claude_mpm/cli/parser.py +36 -0
  22. claude_mpm/cli/utils.py +9 -1
  23. claude_mpm/cli_module/refactoring_guide.md +253 -0
  24. claude_mpm/config/async_logging_config.yaml +145 -0
  25. claude_mpm/constants.py +2 -0
  26. claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +34 -0
  27. claude_mpm/core/agent_registry.py +4 -1
  28. claude_mpm/core/claude_runner.py +297 -20
  29. claude_mpm/core/config_paths.py +0 -1
  30. claude_mpm/core/factories.py +9 -3
  31. claude_mpm/dashboard/.claude-mpm/memories/README.md +36 -0
  32. claude_mpm/dashboard/README.md +121 -0
  33. claude_mpm/dashboard/static/js/dashboard.js.backup +1973 -0
  34. claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +36 -0
  35. claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +39 -0
  36. claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +38 -0
  37. claude_mpm/hooks/README.md +96 -0
  38. claude_mpm/init.py +83 -13
  39. claude_mpm/schemas/agent_schema.json +435 -0
  40. claude_mpm/services/agents/deployment/agent_deployment.py +204 -18
  41. claude_mpm/services/agents/management/agent_management_service.py +2 -1
  42. claude_mpm/services/agents/registry/agent_registry.py +22 -1
  43. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  44. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +3 -3
  45. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +2 -2
  46. claude_mpm/services/version_control/VERSION +1 -0
  47. claude_mpm/validation/agent_validator.py +56 -1
  48. {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/METADATA +60 -3
  49. {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/RECORD +53 -36
  50. {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/WHEEL +0 -0
  51. {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/entry_points.txt +0 -0
  52. {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/licenses/LICENSE +0 -0
  53. {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/top_level.txt +0 -0
@@ -70,13 +70,14 @@ class AgentDeploymentService:
70
70
  - YAML generation capabilities
71
71
  """
72
72
 
73
- def __init__(self, templates_dir: Optional[Path] = None, base_agent_path: Optional[Path] = None):
73
+ def __init__(self, templates_dir: Optional[Path] = None, base_agent_path: Optional[Path] = None, working_directory: Optional[Path] = None):
74
74
  """
75
75
  Initialize agent deployment service.
76
76
 
77
77
  Args:
78
- templates_dir: Directory containing agent template files
78
+ templates_dir: Directory containing agent JSON files
79
79
  base_agent_path: Path to base_agent.md file
80
+ working_directory: User's working directory (for project agents)
80
81
 
81
82
  METRICS OPPORTUNITY: Track initialization performance:
82
83
  - Template directory scan time
@@ -100,11 +101,24 @@ class AgentDeploymentService:
100
101
  'deployment_errors': {} # Track error types and frequencies
101
102
  }
102
103
 
104
+ # Determine the actual working directory
105
+ # Priority: working_directory param > CLAUDE_MPM_USER_PWD env var > current directory
106
+ if working_directory:
107
+ self.working_directory = Path(working_directory)
108
+ elif 'CLAUDE_MPM_USER_PWD' in os.environ:
109
+ self.working_directory = Path(os.environ['CLAUDE_MPM_USER_PWD'])
110
+ else:
111
+ self.working_directory = Path.cwd()
112
+
113
+ self.logger.info(f"Working directory for deployment: {self.working_directory}")
114
+
103
115
  # Find templates directory using centralized path management
104
116
  if templates_dir:
105
117
  self.templates_dir = Path(templates_dir)
106
118
  else:
107
119
  # Use centralized paths instead of fragile parent calculations
120
+ # For system agents, still use templates subdirectory
121
+ # For project/user agents, this should be overridden with actual agents dir
108
122
  self.templates_dir = paths.agents_dir / "templates"
109
123
 
110
124
  # Find base agent file
@@ -135,6 +149,7 @@ class AgentDeploymentService:
135
149
  - Error type frequencies
136
150
 
137
151
  OPERATIONAL FLOW:
152
+ 0. Validates and repairs broken frontmatter in existing agents (Step 0)
138
153
  1. Validates target directory (creates if needed)
139
154
  2. Loads base agent configuration
140
155
  3. Discovers all agent templates
@@ -176,13 +191,15 @@ class AgentDeploymentService:
176
191
  - skipped: List of unchanged agents
177
192
  - errors: List of deployment errors
178
193
  - total: Total number of agents processed
194
+ - repaired: List of agents with repaired frontmatter
179
195
  """
180
196
  # METRICS: Record deployment start time for performance tracking
181
197
  deployment_start_time = time.time()
182
198
 
183
199
  if not target_dir:
184
- # Default to user's home .claude/agents directory
185
- agents_dir = Path(Paths.CLAUDE_AGENTS_DIR.value).expanduser()
200
+ # Default to working directory's .claude/agents directory (not home)
201
+ # This ensures we deploy to the user's project directory, not the framework directory
202
+ agents_dir = self.working_directory / ".claude" / "agents"
186
203
  else:
187
204
  # If target_dir provided, use it directly (caller decides structure)
188
205
  # This allows for both passing a project dir or the full agents path
@@ -209,6 +226,7 @@ class AgentDeploymentService:
209
226
  "updated": [],
210
227
  "migrated": [], # Track agents migrated from old format
211
228
  "converted": [], # Track YAML to MD conversions
229
+ "repaired": [], # Track agents with repaired frontmatter
212
230
  "total": 0,
213
231
  # METRICS: Add detailed timing and performance data to results
214
232
  "metrics": {
@@ -224,13 +242,30 @@ class AgentDeploymentService:
224
242
  try:
225
243
  # Create agents directory if needed
226
244
  agents_dir.mkdir(parents=True, exist_ok=True)
227
- self.logger.info(f"Building and deploying agents to: {agents_dir}")
245
+
246
+ # STEP 0: Validate and repair broken frontmatter in existing agents
247
+ # This ensures all existing agents have valid YAML frontmatter before deploying new ones
248
+ repair_results = self._validate_and_repair_existing_agents(agents_dir)
249
+ if repair_results["repaired"]:
250
+ results["repaired"] = repair_results["repaired"]
251
+ self.logger.info(f"Repaired frontmatter in {len(repair_results['repaired'])} existing agents")
252
+ for agent_name in repair_results["repaired"]:
253
+ self.logger.debug(f" - Repaired: {agent_name}")
254
+
255
+ # Determine source tier for logging
256
+ source_tier = "SYSTEM"
257
+ if ".claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
258
+ source_tier = "PROJECT"
259
+ elif "/.claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
260
+ source_tier = "USER"
261
+
262
+ self.logger.info(f"Building and deploying {source_tier} agents to: {agents_dir}")
228
263
 
229
264
  # Note: System instructions are now loaded directly by SimpleClaudeRunner
230
265
 
231
266
  # Check if templates directory exists
232
267
  if not self.templates_dir.exists():
233
- error_msg = f"Templates directory not found: {self.templates_dir}"
268
+ error_msg = f"Agents directory not found: {self.templates_dir}"
234
269
  self.logger.error(error_msg)
235
270
  results["errors"].append(error_msg)
236
271
  return results
@@ -260,7 +295,8 @@ class AgentDeploymentService:
260
295
  # Get all template files
261
296
  template_files = list(self.templates_dir.glob("*.json"))
262
297
  # Filter out non-agent files - exclude system files and uppercase special files
263
- excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README"}
298
+ # CRITICAL: PM (Project Manager) must NEVER be deployed as it's the main Claude instance
299
+ excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
264
300
  template_files = [
265
301
  f for f in template_files
266
302
  if f.stem not in excluded_names
@@ -364,6 +400,7 @@ class AgentDeploymentService:
364
400
  f"updated {len(results['updated'])}, "
365
401
  f"migrated {len(results['migrated'])}, "
366
402
  f"converted {len(results['converted'])} YAML files, "
403
+ f"repaired {len(results['repaired'])} frontmatter, "
367
404
  f"skipped {len(results['skipped'])}, "
368
405
  f"errors: {len(results['errors'])}"
369
406
  )
@@ -568,20 +605,36 @@ class AgentDeploymentService:
568
605
  # Simplify model name for Claude Code
569
606
  model_map = {
570
607
  'claude-4-sonnet-20250514': 'sonnet',
571
- 'claude-sonnet-4-20250514': 'sonnet',
608
+ 'claude-sonnet-4-20250514': 'sonnet',
609
+ 'claude-opus-4-20250514': 'opus',
572
610
  'claude-3-opus-20240229': 'opus',
573
611
  'claude-3-haiku-20240307': 'haiku',
574
612
  'claude-3.5-sonnet': 'sonnet',
575
613
  'claude-3-sonnet': 'sonnet'
576
614
  }
577
- model = model_map.get(model, model.split('-')[-1] if '-' in model else model)
615
+ # Better fallback: extract the model type (opus/sonnet/haiku) from the string
616
+ if model not in model_map:
617
+ if 'opus' in model.lower():
618
+ model = 'opus'
619
+ elif 'sonnet' in model.lower():
620
+ model = 'sonnet'
621
+ elif 'haiku' in model.lower():
622
+ model = 'haiku'
623
+ else:
624
+ # Last resort: try to extract from hyphenated format
625
+ model = model_map.get(model, model.split('-')[-1] if '-' in model else model)
626
+ else:
627
+ model = model_map[model]
578
628
 
579
629
  # Get response format from template or use base agent default
580
630
  response_format = template_data.get('response', {}).get('format', 'structured')
581
631
 
582
632
  # Convert lists to space-separated strings for Claude Code compatibility
583
633
  tags_str = ' '.join(tags) if isinstance(tags, list) else tags
584
- tools_str = ', '.join(tools) if isinstance(tools, list) else tools
634
+
635
+ # Convert tools list to comma-separated string for Claude Code compatibility
636
+ # IMPORTANT: No spaces after commas - Claude Code requires exact format
637
+ tools_str = ','.join(tools) if isinstance(tools, list) else tools
585
638
 
586
639
  # Build frontmatter with only the fields Claude Code uses
587
640
  frontmatter_lines = [
@@ -596,8 +649,13 @@ class AgentDeploymentService:
596
649
  ]
597
650
 
598
651
  # Add optional fields if present
599
- if template_data.get('color'):
600
- frontmatter_lines.append(f"color: {template_data['color']}")
652
+ # Check for color in metadata section (new format) or root (old format)
653
+ color = (
654
+ template_data.get('metadata', {}).get('color') or
655
+ template_data.get('color')
656
+ )
657
+ if color:
658
+ frontmatter_lines.append(f"color: {color}")
601
659
 
602
660
  frontmatter_lines.append("---")
603
661
  frontmatter_lines.append("")
@@ -834,7 +892,8 @@ temperature: {temperature}"""
834
892
  Dictionary of environment variables set for verification
835
893
  """
836
894
  if not config_dir:
837
- config_dir = Path.cwd() / Paths.CLAUDE_CONFIG_DIR.value
895
+ # Use the working directory determined during initialization
896
+ config_dir = self.working_directory / Paths.CLAUDE_CONFIG_DIR.value
838
897
 
839
898
  env_vars = {}
840
899
 
@@ -900,7 +959,8 @@ temperature: {temperature}"""
900
959
  - warnings: List of potential issues
901
960
  """
902
961
  if not config_dir:
903
- config_dir = Path.cwd() / ".claude"
962
+ # Use the working directory determined during initialization
963
+ config_dir = self.working_directory / ".claude"
904
964
 
905
965
  results = {
906
966
  "config_dir": str(config_dir),
@@ -1059,12 +1119,13 @@ temperature: {temperature}"""
1059
1119
  agents = []
1060
1120
 
1061
1121
  if not self.templates_dir.exists():
1062
- self.logger.warning(f"Templates directory not found: {self.templates_dir}")
1122
+ self.logger.warning(f"Agents directory not found: {self.templates_dir}")
1063
1123
  return agents
1064
1124
 
1065
1125
  template_files = sorted(self.templates_dir.glob("*.json"))
1066
1126
  # Filter out non-agent files - exclude system files and uppercase special files
1067
- excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README"}
1127
+ # CRITICAL: PM (Project Manager) must NEVER be deployed as it's the main Claude instance
1128
+ excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
1068
1129
  template_files = [
1069
1130
  f for f in template_files
1070
1131
  if f.stem not in excluded_names
@@ -1253,7 +1314,8 @@ temperature: {temperature}"""
1253
1314
  Cleanup results
1254
1315
  """
1255
1316
  if not config_dir:
1256
- config_dir = Path.cwd() / ".claude"
1317
+ # Use the working directory determined during initialization
1318
+ config_dir = self.working_directory / ".claude"
1257
1319
 
1258
1320
  results = {
1259
1321
  "removed": [],
@@ -1904,4 +1966,128 @@ metadata:
1904
1966
  except Exception as e:
1905
1967
  self.logger.warning(f"Error extracting YAML field '{field_name}': {e}")
1906
1968
 
1907
- return None
1969
+ return None
1970
+
1971
+ def _validate_and_repair_existing_agents(self, agents_dir: Path) -> Dict[str, Any]:
1972
+ """
1973
+ Validate and repair broken frontmatter in existing agent files.
1974
+
1975
+ This method scans existing .claude/agents/*.md files and validates their
1976
+ frontmatter. If the frontmatter is broken or missing, it attempts to repair
1977
+ it or marks the agent for replacement during deployment.
1978
+
1979
+ WHY: Ensures all existing agents have valid YAML frontmatter before deployment,
1980
+ preventing runtime errors in Claude Code when loading agents.
1981
+
1982
+ Args:
1983
+ agents_dir: Directory containing agent .md files
1984
+
1985
+ Returns:
1986
+ Dictionary with validation results:
1987
+ - repaired: List of agent names that were repaired
1988
+ - replaced: List of agent names marked for replacement
1989
+ - errors: List of validation errors
1990
+ """
1991
+ results = {
1992
+ "repaired": [],
1993
+ "replaced": [],
1994
+ "errors": []
1995
+ }
1996
+
1997
+ try:
1998
+ # Import frontmatter validator
1999
+ from claude_mpm.agents.frontmatter_validator import FrontmatterValidator
2000
+ validator = FrontmatterValidator()
2001
+
2002
+ # Find existing agent files
2003
+ agent_files = list(agents_dir.glob("*.md"))
2004
+
2005
+ if not agent_files:
2006
+ self.logger.debug("No existing agent files to validate")
2007
+ return results
2008
+
2009
+ self.logger.debug(f"Validating frontmatter in {len(agent_files)} existing agents")
2010
+
2011
+ for agent_file in agent_files:
2012
+ try:
2013
+ agent_name = agent_file.stem
2014
+
2015
+ # Read agent file content
2016
+ content = agent_file.read_text()
2017
+
2018
+ # Check if this is a system agent (authored by claude-mpm)
2019
+ # Only repair system agents, leave user agents alone
2020
+ if "author: claude-mpm" not in content and "author: 'claude-mpm'" not in content:
2021
+ self.logger.debug(f"Skipping validation for user agent: {agent_name}")
2022
+ continue
2023
+
2024
+ # Extract and validate frontmatter
2025
+ if not content.startswith("---"):
2026
+ # No frontmatter at all - mark for replacement
2027
+ self.logger.warning(f"Agent {agent_name} has no frontmatter, marking for replacement")
2028
+ results["replaced"].append(agent_name)
2029
+ # Delete the file so it will be recreated
2030
+ agent_file.unlink()
2031
+ continue
2032
+
2033
+ # Try to extract frontmatter
2034
+ try:
2035
+ end_marker = content.find("\n---\n", 4)
2036
+ if end_marker == -1:
2037
+ end_marker = content.find("\n---\r\n", 4)
2038
+
2039
+ if end_marker == -1:
2040
+ # Broken frontmatter - mark for replacement
2041
+ self.logger.warning(f"Agent {agent_name} has broken frontmatter, marking for replacement")
2042
+ results["replaced"].append(agent_name)
2043
+ # Delete the file so it will be recreated
2044
+ agent_file.unlink()
2045
+ continue
2046
+
2047
+ # Validate frontmatter with the validator
2048
+ validation_result = validator.validate_file(agent_file)
2049
+
2050
+ if not validation_result.is_valid:
2051
+ # Check if it can be corrected
2052
+ if validation_result.corrected_frontmatter:
2053
+ # Apply corrections
2054
+ correction_result = validator.correct_file(agent_file, dry_run=False)
2055
+ if correction_result.corrections:
2056
+ results["repaired"].append(agent_name)
2057
+ self.logger.info(f"Repaired frontmatter for agent {agent_name}")
2058
+ for correction in correction_result.corrections:
2059
+ self.logger.debug(f" - {correction}")
2060
+ else:
2061
+ # Cannot be corrected - mark for replacement
2062
+ self.logger.warning(f"Agent {agent_name} has invalid frontmatter that cannot be repaired, marking for replacement")
2063
+ results["replaced"].append(agent_name)
2064
+ # Delete the file so it will be recreated
2065
+ agent_file.unlink()
2066
+ elif validation_result.warnings:
2067
+ # Has warnings but is valid
2068
+ for warning in validation_result.warnings:
2069
+ self.logger.debug(f"Agent {agent_name} warning: {warning}")
2070
+
2071
+ except Exception as e:
2072
+ # Any error in parsing - mark for replacement
2073
+ self.logger.warning(f"Failed to parse frontmatter for {agent_name}: {e}, marking for replacement")
2074
+ results["replaced"].append(agent_name)
2075
+ # Delete the file so it will be recreated
2076
+ try:
2077
+ agent_file.unlink()
2078
+ except Exception:
2079
+ pass
2080
+
2081
+ except Exception as e:
2082
+ error_msg = f"Failed to validate agent {agent_file.name}: {e}"
2083
+ self.logger.error(error_msg)
2084
+ results["errors"].append(error_msg)
2085
+
2086
+ except ImportError:
2087
+ self.logger.warning("FrontmatterValidator not available, skipping validation")
2088
+ except Exception as e:
2089
+ error_msg = f"Agent validation failed: {e}"
2090
+ self.logger.error(error_msg)
2091
+ results["errors"].append(error_msg)
2092
+
2093
+ return results
@@ -57,7 +57,8 @@ class AgentManager:
57
57
 
58
58
  if project_dir is None:
59
59
  project_root = PathResolver.get_project_root()
60
- self.project_dir = project_root / ConfigPaths.CONFIG_DIR / "agents" / "project-specific"
60
+ # Use direct agents directory without subdirectory to match deployment expectations
61
+ self.project_dir = project_root / ConfigPaths.CONFIG_DIR / "agents"
61
62
  else:
62
63
  self.project_dir = project_dir
63
64
  self.version_manager = AgentVersionManager()
@@ -31,6 +31,7 @@ from enum import Enum
31
31
 
32
32
  from claude_mpm.core.config_paths import ConfigPaths
33
33
  from claude_mpm.services.memory.cache.simple_cache import SimpleCacheService
34
+ from claude_mpm.agents.frontmatter_validator import FrontmatterValidator, ValidationResult
34
35
 
35
36
  logger = logging.getLogger(__name__)
36
37
 
@@ -130,6 +131,9 @@ class AgentRegistry:
130
131
 
131
132
  self.model_selector = model_selector
132
133
 
134
+ # Initialize frontmatter validator
135
+ self.frontmatter_validator = FrontmatterValidator()
136
+
133
137
  # Registry storage
134
138
  self.registry: Dict[str, AgentMetadata] = {}
135
139
  self.discovery_paths: List[Path] = []
@@ -337,10 +341,27 @@ class AgentRegistry:
337
341
  if len(parts) >= 3:
338
342
  frontmatter_text = parts[1].strip()
339
343
  data = yaml.safe_load(frontmatter_text)
344
+
345
+ # Validate and correct frontmatter
346
+ validation_result = self.frontmatter_validator.validate_and_correct(data)
347
+ if validation_result.corrections:
348
+ logger.info(f"Applied corrections to {file_path.name}:")
349
+ for correction in validation_result.corrections:
350
+ logger.info(f" - {correction}")
351
+
352
+ # Use corrected frontmatter if available
353
+ if validation_result.corrected_frontmatter:
354
+ data = validation_result.corrected_frontmatter
355
+
356
+ if validation_result.errors:
357
+ logger.warning(f"Validation errors in {file_path.name}:")
358
+ for error in validation_result.errors:
359
+ logger.warning(f" - {error}")
360
+
340
361
  description = data.get('description', '')
341
362
  version = data.get('version', '0.0.0')
342
363
  capabilities = data.get('tools', []) # Tools in .md format
343
- metadata = data.get('metadata', {})
364
+ metadata = data
344
365
  else:
345
366
  # No frontmatter, use defaults
346
367
  description = f"{file_path.stem} agent"
@@ -0,0 +1,92 @@
1
+ # Framework CLAUDE.md Generator
2
+
3
+ This directory contains the refactored framework CLAUDE.md generator service, originally a single 1,267-line file.
4
+
5
+ ## Structure
6
+
7
+ ### Core Modules
8
+
9
+ - **`__init__.py`** (~250 lines) - Main facade class that coordinates all functionality
10
+ - **`version_manager.py`** (~100 lines) - Handles version parsing, incrementing, and comparison
11
+ - **`content_assembler.py`** (~120 lines) - Assembles sections and applies template variables
12
+ - **`content_validator.py`** (~80 lines) - Validates generated content structure and completeness
13
+ - **`deployment_manager.py`** (~80 lines) - Handles deployment to parent directories
14
+ - **`section_manager.py`** (~60 lines) - Manages section registration, ordering, and updates
15
+
16
+ ### Section Generators
17
+
18
+ The `section_generators/` subdirectory contains individual generators for each section:
19
+
20
+ - **`__init__.py`** - Base classes and registry
21
+ - **`header.py`** - Header with version metadata
22
+ - **`role_designation.py`** - AI Assistant role designation
23
+ - **`agents.py`** (~570 lines) - Comprehensive agents documentation (largest section)
24
+ - **`todo_task_tools.py`** - Todo and Task Tools integration
25
+ - **`claude_pm_init.py`** - Claude-PM initialization procedures
26
+ - **`orchestration_principles.py`** - Core orchestration principles
27
+ - **`subprocess_validation.py`** - Subprocess validation protocol
28
+ - **`delegation_constraints.py`** - Critical delegation constraints
29
+ - **`environment_config.py`** - Environment configuration
30
+ - **`troubleshooting.py`** - Troubleshooting guide
31
+ - **`core_responsibilities.py`** - Core responsibilities list
32
+ - **`footer.py`** - Footer with metadata
33
+
34
+ ## Usage
35
+
36
+ The API remains unchanged from the original implementation:
37
+
38
+ ```python
39
+ from claude_pm.services.framework_claude_md_generator import FrameworkClaudeMdGenerator
40
+
41
+ # Create generator instance
42
+ generator = FrameworkClaudeMdGenerator()
43
+
44
+ # Generate content
45
+ content = generator.generate(
46
+ current_content=existing_content, # Optional: for version auto-increment
47
+ template_variables={'PLATFORM': 'darwin', 'PYTHON_CMD': 'python3'}
48
+ )
49
+
50
+ # Deploy to parent directory
51
+ success, message = generator.deploy_to_parent(Path('/parent/dir'))
52
+
53
+ # Update a section
54
+ generator.update_section('agents', 'Custom agents content')
55
+
56
+ # Add custom section
57
+ generator.add_custom_section('custom', 'Custom content', after='agents')
58
+ ```
59
+
60
+ ## Design Benefits
61
+
62
+ 1. **Modularity**: Each concern is separated into its own module
63
+ 2. **Maintainability**: Smaller, focused modules are easier to understand and modify
64
+ 3. **Testability**: Individual components can be tested in isolation
65
+ 4. **Extensibility**: New section generators can be added easily
66
+ 5. **Performance**: Section generators are loaded on demand
67
+ 6. **Backward Compatibility**: Original API preserved through facade pattern
68
+
69
+ ## Section Generator Pattern
70
+
71
+ To add a new section generator:
72
+
73
+ 1. Create a new file in `section_generators/`
74
+ 2. Inherit from `BaseSectionGenerator`
75
+ 3. Implement the `generate()` method
76
+ 4. Register in `section_generators/__init__.py`
77
+
78
+ Example:
79
+ ```python
80
+ from . import BaseSectionGenerator
81
+
82
+ class CustomSectionGenerator(BaseSectionGenerator):
83
+ def generate(self, data: Dict[str, Any]) -> str:
84
+ return "## Custom Section\n\nContent here..."
85
+ ```
86
+
87
+ ## Refactoring Summary
88
+
89
+ - **Original**: 1,267 lines in a single file
90
+ - **Refactored**: ~250 lines in main module + well-organized submodules
91
+ - **Total line reduction**: Main module reduced by 80%
92
+ - **Improved organization**: 13 focused modules instead of 1 large file
@@ -75,8 +75,8 @@ class AgentsGenerator(BaseSectionGenerator):
75
75
  **ALL AGENT OPERATIONS FOLLOW HIERARCHICAL PRECEDENCE**
76
76
 
77
77
  #### Agent Hierarchy (Highest to Lowest Priority)
78
- 1. **Project Agents**: `$PROJECT/.claude-mpm/agents/project-specific/`
79
- - Project-specific implementations and overrides
78
+ 1. **Project Agents**: `$PROJECT/.claude-mpm/agents/`
79
+ - Project implementations and overrides
80
80
  - Highest precedence for project context
81
81
  - Custom agents tailored to project requirements
82
82
 
@@ -376,7 +376,7 @@ for agent_id, metadata in agents.items():
376
376
  #### Directory Precedence Rules and Agent Discovery
377
377
 
378
378
  **Enhanced Agent Discovery Pattern (Highest to Lowest Priority):**
379
- 1. **Project Agents**: `$PROJECT/.claude-mpm/agents/project-specific/`
379
+ 1. **Project Agents**: `$PROJECT/.claude-mpm/agents/`
380
380
  2. **Current Directory User Agents**: `$PWD/.claude-mpm/agents/user-agents/`
381
381
  3. **Parent Directory User Agents**: Walk up tree checking `../user-agents/`, `../../user-agents/`, etc.
382
382
  4. **User Home Agents**: `~/.claude-mpm/agents/user-defined/`
@@ -79,9 +79,9 @@ claude-pm init --verify
79
79
  - Working directory context
80
80
 
81
81
  3. **Project Directory** (`$PROJECT_ROOT/.claude-mpm/`)
82
- - Project-specific agents in `agents/project-specific/`
82
+ - Project agents in `agents/`
83
83
  - User agents in `agents/user-agents/` with directory precedence
84
- - Project-specific configuration
84
+ - Project configuration
85
85
 
86
86
  ### Health Validation and Deployment Procedures
87
87
 
@@ -0,0 +1 @@
1
+ 0.9.0
@@ -53,6 +53,23 @@ class AgentValidator:
53
53
  - Privilege escalation (via tool combinations)
54
54
  """
55
55
 
56
+ # Model name mappings for normalization to tier names
57
+ MODEL_MAPPINGS = {
58
+ # Sonnet variations
59
+ "claude-3-5-sonnet-20241022": "sonnet",
60
+ "claude-3-5-sonnet-20240620": "sonnet",
61
+ "claude-sonnet-4-20250514": "sonnet",
62
+ "claude-4-sonnet-20250514": "sonnet",
63
+ "claude-3-sonnet-20240229": "sonnet",
64
+ # Opus variations
65
+ "claude-3-opus-20240229": "opus",
66
+ "claude-opus-4-20250514": "opus",
67
+ "claude-4-opus-20250514": "opus",
68
+ # Haiku variations
69
+ "claude-3-haiku-20240307": "haiku",
70
+ "claude-3-5-haiku-20241022": "haiku",
71
+ }
72
+
56
73
  def __init__(self, schema_path: Optional[Path] = None):
57
74
  """Initialize the validator with the agent schema."""
58
75
  if schema_path is None:
@@ -83,6 +100,33 @@ class AgentValidator:
83
100
  logger.error(f"Failed to load schema from {self.schema_path}: {e}")
84
101
  raise
85
102
 
103
+ def _normalize_model(self, model: str) -> str:
104
+ """Normalize model name to standard tier (opus, sonnet, haiku).
105
+
106
+ Args:
107
+ model: Original model name
108
+
109
+ Returns:
110
+ Normalized model tier name
111
+ """
112
+ # Direct mapping check
113
+ if model in self.MODEL_MAPPINGS:
114
+ return self.MODEL_MAPPINGS[model]
115
+
116
+ # Already normalized
117
+ if model in {"opus", "sonnet", "haiku"}:
118
+ return model
119
+
120
+ # Check if model contains tier name
121
+ model_lower = model.lower()
122
+ for tier in {"opus", "sonnet", "haiku"}:
123
+ if tier in model_lower:
124
+ return tier
125
+
126
+ # Default to sonnet if unrecognized
127
+ logger.warning(f"Unrecognized model '{model}', defaulting to 'sonnet'")
128
+ return "sonnet"
129
+
86
130
  def validate_agent(self, agent_data: Dict[str, Any]) -> ValidationResult:
87
131
  """
88
132
  Validate a single agent configuration against the schema.
@@ -101,6 +145,14 @@ class AgentValidator:
101
145
  """
102
146
  result = ValidationResult(is_valid=True)
103
147
 
148
+ # Normalize model name before validation
149
+ if "capabilities" in agent_data and "model" in agent_data["capabilities"]:
150
+ original_model = agent_data["capabilities"]["model"]
151
+ normalized_model = self._normalize_model(original_model)
152
+ if original_model != normalized_model:
153
+ agent_data["capabilities"]["model"] = normalized_model
154
+ result.warnings.append(f"Normalized model from '{original_model}' to '{normalized_model}'")
155
+
104
156
  # Perform JSON schema validation
105
157
  try:
106
158
  validate(instance=agent_data, schema=self.schema)
@@ -242,8 +294,11 @@ class AgentValidator:
242
294
  model = agent_data.get("capabilities", {}).get("model", "")
243
295
  tools = agent_data.get("capabilities", {}).get("tools", [])
244
296
 
297
+ # Normalize model name for comparison
298
+ normalized_model = self._normalize_model(model)
299
+
245
300
  # Haiku models shouldn't use resource-intensive tools
246
- if "haiku" in model.lower():
301
+ if normalized_model == "haiku":
247
302
  intensive_tools = {"docker", "kubectl", "terraform", "aws", "gcloud", "azure"}
248
303
  used_intensive = set(tools) & intensive_tools
249
304
  if used_intensive: