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.
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +10 -0
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +14 -13
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/agents/agent_loader.py +109 -15
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +448 -0
- claude_mpm/agents/templates/data_engineer.json +4 -3
- claude_mpm/agents/templates/documentation.json +4 -3
- claude_mpm/agents/templates/engineer.json +4 -3
- claude_mpm/agents/templates/ops.json +4 -3
- claude_mpm/agents/templates/pm.json +5 -4
- claude_mpm/agents/templates/qa.json +4 -3
- claude_mpm/agents/templates/research.json +8 -7
- claude_mpm/agents/templates/security.json +4 -3
- claude_mpm/agents/templates/test_integration.json +4 -3
- claude_mpm/agents/templates/version_control.json +4 -3
- claude_mpm/cli/README.md +108 -0
- claude_mpm/cli/commands/agents.py +373 -7
- claude_mpm/cli/commands/run.py +4 -11
- claude_mpm/cli/parser.py +36 -0
- claude_mpm/cli/utils.py +9 -1
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/config/async_logging_config.yaml +145 -0
- claude_mpm/constants.py +2 -0
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +34 -0
- claude_mpm/core/agent_registry.py +4 -1
- claude_mpm/core/claude_runner.py +297 -20
- claude_mpm/core/config_paths.py +0 -1
- claude_mpm/core/factories.py +9 -3
- claude_mpm/dashboard/.claude-mpm/memories/README.md +36 -0
- claude_mpm/dashboard/README.md +121 -0
- claude_mpm/dashboard/static/js/dashboard.js.backup +1973 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +36 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +39 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +38 -0
- claude_mpm/hooks/README.md +96 -0
- claude_mpm/init.py +83 -13
- claude_mpm/schemas/agent_schema.json +435 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +204 -18
- claude_mpm/services/agents/management/agent_management_service.py +2 -1
- claude_mpm/services/agents/registry/agent_registry.py +22 -1
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +2 -2
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/validation/agent_validator.py +56 -1
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/METADATA +60 -3
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/RECORD +53 -36
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/WHEEL +0 -0
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/top_level.txt +0 -0
    
        claude_mpm/core/claude_runner.py
    CHANGED
    
    | @@ -74,8 +74,16 @@ class ClaudeRunner: | |
| 74 74 | 
             
                            self.logger.warning(f"Failed to initialize project logger: {e}")
         | 
| 75 75 |  | 
| 76 76 | 
             
                    # Initialize services with proper error handling
         | 
| 77 | 
            +
                    # Determine the user's working directory from environment
         | 
| 78 | 
            +
                    user_working_dir = None
         | 
| 79 | 
            +
                    if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 80 | 
            +
                        user_working_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 81 | 
            +
                        self.logger.info(f"Using user working directory from CLAUDE_MPM_USER_PWD: {user_working_dir}")
         | 
| 82 | 
            +
                    
         | 
| 77 83 | 
             
                    try:
         | 
| 78 | 
            -
                         | 
| 84 | 
            +
                        # Pass the user working directory to the deployment service
         | 
| 85 | 
            +
                        # This ensures agents are deployed to the correct user directory, not the framework directory
         | 
| 86 | 
            +
                        self.deployment_service = AgentDeploymentService(working_directory=user_working_dir)
         | 
| 79 87 | 
             
                    except ImportError as e:
         | 
| 80 88 | 
             
                        self.logger.error(f"Failed to import AgentDeploymentService: {e}")
         | 
| 81 89 | 
             
                        raise RuntimeError("Required module AgentDeploymentService not available. Please reinstall claude-mpm.") from e
         | 
| @@ -110,6 +118,22 @@ class ClaudeRunner: | |
| 110 118 | 
             
                        self.logger.error(f"Failed to load configuration: {e}")
         | 
| 111 119 | 
             
                        raise RuntimeError(f"Configuration initialization failed: {e}") from e
         | 
| 112 120 |  | 
| 121 | 
            +
                    # Initialize response logging if enabled
         | 
| 122 | 
            +
                    self.response_logger = None
         | 
| 123 | 
            +
                    response_config = self.config.get('response_logging', {})
         | 
| 124 | 
            +
                    if response_config.get('enabled', False):
         | 
| 125 | 
            +
                        try:
         | 
| 126 | 
            +
                            from claude_mpm.services.claude_session_logger import get_session_logger
         | 
| 127 | 
            +
                            self.response_logger = get_session_logger(self.config)
         | 
| 128 | 
            +
                            if self.project_logger:
         | 
| 129 | 
            +
                                self.project_logger.log_system(
         | 
| 130 | 
            +
                                    "Response logging initialized",
         | 
| 131 | 
            +
                                    level="INFO",
         | 
| 132 | 
            +
                                    component="logging"
         | 
| 133 | 
            +
                                )
         | 
| 134 | 
            +
                        except Exception as e:
         | 
| 135 | 
            +
                            self.logger.warning(f"Failed to initialize response logger: {e}")
         | 
| 136 | 
            +
                    
         | 
| 113 137 | 
             
                    # Initialize hook service
         | 
| 114 138 | 
             
                    try:
         | 
| 115 139 | 
             
                        self.hook_service = HookService(self.config)
         | 
| @@ -199,7 +223,7 @@ class ClaudeRunner: | |
| 199 223 | 
             
                        return False
         | 
| 200 224 |  | 
| 201 225 | 
             
                    except FileNotFoundError as e:
         | 
| 202 | 
            -
                        error_msg = f"Agent  | 
| 226 | 
            +
                        error_msg = f"Agent files not found: {e}"
         | 
| 203 227 | 
             
                        self.logger.error(error_msg)
         | 
| 204 228 | 
             
                        print(f"❌ {error_msg}")
         | 
| 205 229 | 
             
                        print("💡 Ensure claude-mpm is properly installed")
         | 
| @@ -228,16 +252,21 @@ class ClaudeRunner: | |
| 228 252 | 
             
                def ensure_project_agents(self) -> bool:
         | 
| 229 253 | 
             
                    """Ensure system agents are available in the project directory.
         | 
| 230 254 |  | 
| 231 | 
            -
                    Deploys system agents to project's .claude | 
| 232 | 
            -
                    if they don't exist or are outdated. This  | 
| 233 | 
            -
                     | 
| 255 | 
            +
                    Deploys system agents to project's .claude/agents/ directory
         | 
| 256 | 
            +
                    if they don't exist or are outdated. This ensures agents are
         | 
| 257 | 
            +
                    available for Claude Code to use. Project-specific JSON templates
         | 
| 258 | 
            +
                    should be placed in .claude-mpm/agents/.
         | 
| 234 259 |  | 
| 235 260 | 
             
                    Returns:
         | 
| 236 261 | 
             
                        bool: True if agents are available, False on error
         | 
| 237 262 | 
             
                    """
         | 
| 238 263 | 
             
                    try:
         | 
| 239 | 
            -
                        #  | 
| 240 | 
            -
                         | 
| 264 | 
            +
                        # Use the correct user directory, not the framework directory
         | 
| 265 | 
            +
                        if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 266 | 
            +
                            project_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 267 | 
            +
                        else:
         | 
| 268 | 
            +
                            project_dir = Path.cwd()
         | 
| 269 | 
            +
                        
         | 
| 241 270 | 
             
                        project_agents_dir = project_dir / ".claude-mpm" / "agents"
         | 
| 242 271 |  | 
| 243 272 | 
             
                        # Create directory if it doesn't exist
         | 
| @@ -250,10 +279,12 @@ class ClaudeRunner: | |
| 250 279 | 
             
                                component="deployment"
         | 
| 251 280 | 
             
                            )
         | 
| 252 281 |  | 
| 253 | 
            -
                        # Deploy agents to project directory  | 
| 282 | 
            +
                        # Deploy agents to project's .claude/agents directory (not .claude-mpm)
         | 
| 254 283 | 
             
                        # This ensures all system agents are deployed regardless of version
         | 
| 284 | 
            +
                        # .claude-mpm/agents/ should only contain JSON source templates
         | 
| 285 | 
            +
                        # .claude/agents/ should contain the built MD files for Claude Code
         | 
| 255 286 | 
             
                        results = self.deployment_service.deploy_agents(
         | 
| 256 | 
            -
                            target_dir=project_dir / ".claude | 
| 287 | 
            +
                            target_dir=project_dir / ".claude",
         | 
| 257 288 | 
             
                            force_rebuild=False,
         | 
| 258 289 | 
             
                            deployment_mode="project"
         | 
| 259 290 | 
             
                        )
         | 
| @@ -286,8 +317,164 @@ class ClaudeRunner: | |
| 286 317 | 
             
                            )
         | 
| 287 318 | 
             
                        return False
         | 
| 288 319 |  | 
| 320 | 
            +
                def deploy_project_agents_to_claude(self) -> bool:
         | 
| 321 | 
            +
                    """Deploy project agents from .claude-mpm/agents/ to .claude/agents/.
         | 
| 322 | 
            +
                    
         | 
| 323 | 
            +
                    This method handles the deployment of project-specific agents (JSON format)
         | 
| 324 | 
            +
                    from the project's agents directory to Claude's agent directory.
         | 
| 325 | 
            +
                    Project agents take precedence over system agents.
         | 
| 326 | 
            +
                    
         | 
| 327 | 
            +
                    WHY: Project agents allow teams to define custom, project-specific agents
         | 
| 328 | 
            +
                    that override system agents. These are stored in JSON format in 
         | 
| 329 | 
            +
                    .claude-mpm/agents/ and need to be deployed to .claude/agents/
         | 
| 330 | 
            +
                    as MD files for Claude to use them.
         | 
| 331 | 
            +
                    
         | 
| 332 | 
            +
                    Returns:
         | 
| 333 | 
            +
                        bool: True if deployment successful or no agents to deploy, False on error
         | 
| 334 | 
            +
                    """
         | 
| 335 | 
            +
                    try:
         | 
| 336 | 
            +
                        # Use the correct user directory, not the framework directory
         | 
| 337 | 
            +
                        if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 338 | 
            +
                            project_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 339 | 
            +
                        else:
         | 
| 340 | 
            +
                            project_dir = Path.cwd()
         | 
| 341 | 
            +
                        
         | 
| 342 | 
            +
                        project_agents_dir = project_dir / ".claude-mpm" / "agents"
         | 
| 343 | 
            +
                        claude_agents_dir = project_dir / ".claude" / "agents"
         | 
| 344 | 
            +
                        
         | 
| 345 | 
            +
                        # Check if project agents directory exists
         | 
| 346 | 
            +
                        if not project_agents_dir.exists():
         | 
| 347 | 
            +
                            self.logger.debug("No project agents directory found")
         | 
| 348 | 
            +
                            return True  # Not an error - just no project agents
         | 
| 349 | 
            +
                        
         | 
| 350 | 
            +
                        # Get JSON agent files from agents directory
         | 
| 351 | 
            +
                        json_files = list(project_agents_dir.glob("*.json"))
         | 
| 352 | 
            +
                        if not json_files:
         | 
| 353 | 
            +
                            self.logger.debug("No JSON agents in project")
         | 
| 354 | 
            +
                            return True
         | 
| 355 | 
            +
                        
         | 
| 356 | 
            +
                        # Create .claude/agents directory if needed
         | 
| 357 | 
            +
                        claude_agents_dir.mkdir(parents=True, exist_ok=True)
         | 
| 358 | 
            +
                        
         | 
| 359 | 
            +
                        self.logger.info(f"Deploying {len(json_files)} project agents to .claude/agents/")
         | 
| 360 | 
            +
                        if self.project_logger:
         | 
| 361 | 
            +
                            self.project_logger.log_system(
         | 
| 362 | 
            +
                                f"Deploying project agents from {project_agents_dir} to {claude_agents_dir}",
         | 
| 363 | 
            +
                                level="INFO",
         | 
| 364 | 
            +
                                component="deployment"
         | 
| 365 | 
            +
                            )
         | 
| 366 | 
            +
                        
         | 
| 367 | 
            +
                        deployed_count = 0
         | 
| 368 | 
            +
                        updated_count = 0
         | 
| 369 | 
            +
                        errors = []
         | 
| 370 | 
            +
                        
         | 
| 371 | 
            +
                        # Deploy each JSON agent
         | 
| 372 | 
            +
                        # CRITICAL: PM (Project Manager) must NEVER be deployed as it's the main Claude instance
         | 
| 373 | 
            +
                        EXCLUDED_AGENTS = {'pm', 'project_manager'}
         | 
| 374 | 
            +
                        
         | 
| 375 | 
            +
                        # Initialize deployment service with proper base agent path
         | 
| 376 | 
            +
                        # Use the existing deployment service's base agent path if available
         | 
| 377 | 
            +
                        base_agent_path = project_agents_dir / "base_agent.json"
         | 
| 378 | 
            +
                        if not base_agent_path.exists():
         | 
| 379 | 
            +
                            # Fall back to system base agent
         | 
| 380 | 
            +
                            base_agent_path = self.deployment_service.base_agent_path
         | 
| 381 | 
            +
                        
         | 
| 382 | 
            +
                        # Create a single deployment service instance for all agents
         | 
| 383 | 
            +
                        project_deployment = AgentDeploymentService(
         | 
| 384 | 
            +
                            templates_dir=project_agents_dir,
         | 
| 385 | 
            +
                            base_agent_path=base_agent_path,
         | 
| 386 | 
            +
                            working_directory=project_dir  # Pass the project directory
         | 
| 387 | 
            +
                        )
         | 
| 388 | 
            +
                        
         | 
| 389 | 
            +
                        # Load base agent data once
         | 
| 390 | 
            +
                        base_agent_data = {}
         | 
| 391 | 
            +
                        if base_agent_path and base_agent_path.exists():
         | 
| 392 | 
            +
                            try:
         | 
| 393 | 
            +
                                import json
         | 
| 394 | 
            +
                                base_agent_data = json.loads(base_agent_path.read_text())
         | 
| 395 | 
            +
                            except Exception as e:
         | 
| 396 | 
            +
                                self.logger.warning(f"Could not load base agent: {e}")
         | 
| 397 | 
            +
                        
         | 
| 398 | 
            +
                        for json_file in json_files:
         | 
| 399 | 
            +
                            try:
         | 
| 400 | 
            +
                                agent_name = json_file.stem
         | 
| 401 | 
            +
                                
         | 
| 402 | 
            +
                                # Skip PM agent - it's the main Claude instance, not a subagent
         | 
| 403 | 
            +
                                if agent_name.lower() in EXCLUDED_AGENTS:
         | 
| 404 | 
            +
                                    self.logger.info(f"Skipping {agent_name} (PM is the main Claude instance)")
         | 
| 405 | 
            +
                                    continue
         | 
| 406 | 
            +
                                
         | 
| 407 | 
            +
                                target_file = claude_agents_dir / f"{agent_name}.md"
         | 
| 408 | 
            +
                                
         | 
| 409 | 
            +
                                # Check if agent needs update
         | 
| 410 | 
            +
                                needs_update = True
         | 
| 411 | 
            +
                                if target_file.exists():
         | 
| 412 | 
            +
                                    # Check if it's a project agent (has project marker)
         | 
| 413 | 
            +
                                    existing_content = target_file.read_text()
         | 
| 414 | 
            +
                                    if "author: claude-mpm-project" in existing_content or "source: project" in existing_content:
         | 
| 415 | 
            +
                                        # Compare modification times
         | 
| 416 | 
            +
                                        if target_file.stat().st_mtime >= json_file.stat().st_mtime:
         | 
| 417 | 
            +
                                            needs_update = False
         | 
| 418 | 
            +
                                            self.logger.debug(f"Project agent {agent_name} is up to date")
         | 
| 419 | 
            +
                                
         | 
| 420 | 
            +
                                if needs_update:
         | 
| 421 | 
            +
                                    # Build the agent markdown using the pre-initialized service and base agent data
         | 
| 422 | 
            +
                                    agent_content = project_deployment._build_agent_markdown(
         | 
| 423 | 
            +
                                        agent_name, json_file, base_agent_data
         | 
| 424 | 
            +
                                    )
         | 
| 425 | 
            +
                                    
         | 
| 426 | 
            +
                                    # Mark as project agent
         | 
| 427 | 
            +
                                    agent_content = agent_content.replace(
         | 
| 428 | 
            +
                                        "author: claude-mpm",
         | 
| 429 | 
            +
                                        "author: claude-mpm-project"
         | 
| 430 | 
            +
                                    )
         | 
| 431 | 
            +
                                    
         | 
| 432 | 
            +
                                    # Write the agent file
         | 
| 433 | 
            +
                                    is_update = target_file.exists()
         | 
| 434 | 
            +
                                    target_file.write_text(agent_content)
         | 
| 435 | 
            +
                                    
         | 
| 436 | 
            +
                                    if is_update:
         | 
| 437 | 
            +
                                        updated_count += 1
         | 
| 438 | 
            +
                                        self.logger.info(f"Updated project agent: {agent_name}")
         | 
| 439 | 
            +
                                    else:
         | 
| 440 | 
            +
                                        deployed_count += 1
         | 
| 441 | 
            +
                                        self.logger.info(f"Deployed project agent: {agent_name}")
         | 
| 442 | 
            +
                                        
         | 
| 443 | 
            +
                            except Exception as e:
         | 
| 444 | 
            +
                                error_msg = f"Failed to deploy project agent {json_file.name}: {e}"
         | 
| 445 | 
            +
                                self.logger.error(error_msg)
         | 
| 446 | 
            +
                                errors.append(error_msg)
         | 
| 447 | 
            +
                        
         | 
| 448 | 
            +
                        # Report results
         | 
| 449 | 
            +
                        if deployed_count > 0 or updated_count > 0:
         | 
| 450 | 
            +
                            print(f"✓ Deployed {deployed_count} project agents, updated {updated_count}")
         | 
| 451 | 
            +
                            if self.project_logger:
         | 
| 452 | 
            +
                                self.project_logger.log_system(
         | 
| 453 | 
            +
                                    f"Project agent deployment: {deployed_count} deployed, {updated_count} updated",
         | 
| 454 | 
            +
                                    level="INFO",
         | 
| 455 | 
            +
                                    component="deployment"
         | 
| 456 | 
            +
                                )
         | 
| 457 | 
            +
                        
         | 
| 458 | 
            +
                        if errors:
         | 
| 459 | 
            +
                            for error in errors:
         | 
| 460 | 
            +
                                print(f"⚠️  {error}")
         | 
| 461 | 
            +
                            return False
         | 
| 462 | 
            +
                        
         | 
| 463 | 
            +
                        return True
         | 
| 464 | 
            +
                        
         | 
| 465 | 
            +
                    except Exception as e:
         | 
| 466 | 
            +
                        error_msg = f"Failed to deploy project agents: {e}"
         | 
| 467 | 
            +
                        self.logger.error(error_msg)
         | 
| 468 | 
            +
                        print(f"⚠️  {error_msg}")
         | 
| 469 | 
            +
                        if self.project_logger:
         | 
| 470 | 
            +
                            self.project_logger.log_system(error_msg, level="ERROR", component="deployment")
         | 
| 471 | 
            +
                        return False
         | 
| 472 | 
            +
                
         | 
| 289 473 | 
             
                def run_interactive(self, initial_context: Optional[str] = None):
         | 
| 290 474 | 
             
                    """Run Claude in interactive mode."""
         | 
| 475 | 
            +
                    # TODO: Add response logging for interactive mode
         | 
| 476 | 
            +
                    # This requires capturing stdout from the exec'd process or using subprocess with PTY
         | 
| 477 | 
            +
                    
         | 
| 291 478 | 
             
                    # Connect to Socket.IO server if enabled
         | 
| 292 479 | 
             
                    if self.enable_websocket:
         | 
| 293 480 | 
             
                        try:
         | 
| @@ -336,10 +523,13 @@ class ClaudeRunner: | |
| 336 523 | 
             
                            component="session"
         | 
| 337 524 | 
             
                        )
         | 
| 338 525 |  | 
| 339 | 
            -
                    # Setup agents
         | 
| 526 | 
            +
                    # Setup agents - first deploy system agents, then project agents
         | 
| 340 527 | 
             
                    if not self.setup_agents():
         | 
| 341 528 | 
             
                        print("Continuing without native agents...")
         | 
| 342 529 |  | 
| 530 | 
            +
                    # Deploy project-specific agents if they exist
         | 
| 531 | 
            +
                    self.deploy_project_agents_to_claude()
         | 
| 532 | 
            +
                    
         | 
| 343 533 | 
             
                    # Build command with system instructions
         | 
| 344 534 | 
             
                    cmd = [
         | 
| 345 535 | 
             
                        "claude",
         | 
| @@ -587,10 +777,13 @@ class ClaudeRunner: | |
| 587 777 | 
             
                            component="session"
         | 
| 588 778 | 
             
                        )
         | 
| 589 779 |  | 
| 590 | 
            -
                    # Setup agents
         | 
| 780 | 
            +
                    # Setup agents - first deploy system agents, then project agents
         | 
| 591 781 | 
             
                    if not self.setup_agents():
         | 
| 592 782 | 
             
                        print("Continuing without native agents...")
         | 
| 593 783 |  | 
| 784 | 
            +
                    # Deploy project-specific agents if they exist
         | 
| 785 | 
            +
                    self.deploy_project_agents_to_claude()
         | 
| 786 | 
            +
                    
         | 
| 594 787 | 
             
                    # Combine context and prompt
         | 
| 595 788 | 
             
                    full_prompt = prompt
         | 
| 596 789 | 
             
                    if context:
         | 
| @@ -671,6 +864,22 @@ class ClaudeRunner: | |
| 671 864 | 
             
                            response = result.stdout.strip()
         | 
| 672 865 | 
             
                            print(response)
         | 
| 673 866 |  | 
| 867 | 
            +
                            # Log response if logging enabled
         | 
| 868 | 
            +
                            if self.response_logger and response:
         | 
| 869 | 
            +
                                execution_time = time.time() - start_time
         | 
| 870 | 
            +
                                response_summary = prompt[:200] + "..." if len(prompt) > 200 else prompt
         | 
| 871 | 
            +
                                self.response_logger.log_response(
         | 
| 872 | 
            +
                                    request_summary=response_summary,
         | 
| 873 | 
            +
                                    response_content=response,
         | 
| 874 | 
            +
                                    metadata={
         | 
| 875 | 
            +
                                        "mode": "oneshot",
         | 
| 876 | 
            +
                                        "model": "opus",
         | 
| 877 | 
            +
                                        "exit_code": result.returncode,
         | 
| 878 | 
            +
                                        "execution_time": execution_time
         | 
| 879 | 
            +
                                    },
         | 
| 880 | 
            +
                                    agent="claude-direct"
         | 
| 881 | 
            +
                                )
         | 
| 882 | 
            +
                            
         | 
| 674 883 | 
             
                            # Broadcast output to WebSocket clients
         | 
| 675 884 | 
             
                            if self.websocket_server and response:
         | 
| 676 885 | 
             
                                self.websocket_server.claude_output(response, "stdout")
         | 
| @@ -884,17 +1093,46 @@ class ClaudeRunner: | |
| 884 1093 | 
             
                def _load_system_instructions(self) -> Optional[str]:
         | 
| 885 1094 | 
             
                    """Load and process system instructions from agents/INSTRUCTIONS.md.
         | 
| 886 1095 |  | 
| 887 | 
            -
                     | 
| 888 | 
            -
                     | 
| 1096 | 
            +
                    Implements project > framework precedence:
         | 
| 1097 | 
            +
                    1. First check for project-specific instructions in .claude-mpm/agents/INSTRUCTIONS.md
         | 
| 1098 | 
            +
                    2. If not found, fall back to framework instructions in src/claude_mpm/agents/INSTRUCTIONS.md
         | 
| 1099 | 
            +
                    
         | 
| 1100 | 
            +
                    WHY: Allows projects to override the default PM instructions with project-specific
         | 
| 1101 | 
            +
                    guidance, while maintaining backward compatibility with the framework defaults.
         | 
| 1102 | 
            +
                    
         | 
| 1103 | 
            +
                    DESIGN DECISION: Using CLAUDE_MPM_USER_PWD environment variable to locate the
         | 
| 1104 | 
            +
                    correct project directory, ensuring we check the right location even when
         | 
| 1105 | 
            +
                    claude-mpm is invoked from a different directory.
         | 
| 889 1106 | 
             
                    """
         | 
| 890 1107 | 
             
                    try:
         | 
| 891 | 
            -
                        #  | 
| 892 | 
            -
                         | 
| 893 | 
            -
             | 
| 1108 | 
            +
                        # Determine the user's project directory
         | 
| 1109 | 
            +
                        if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 1110 | 
            +
                            project_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 1111 | 
            +
                        else:
         | 
| 1112 | 
            +
                            project_dir = Path.cwd()
         | 
| 894 1113 |  | 
| 895 | 
            -
                         | 
| 896 | 
            -
             | 
| 897 | 
            -
             | 
| 1114 | 
            +
                        # Check for project-specific INSTRUCTIONS.md first
         | 
| 1115 | 
            +
                        project_instructions_path = project_dir / ".claude-mpm" / "agents" / "INSTRUCTIONS.md"
         | 
| 1116 | 
            +
                        
         | 
| 1117 | 
            +
                        instructions_path = None
         | 
| 1118 | 
            +
                        instructions_source = None
         | 
| 1119 | 
            +
                        
         | 
| 1120 | 
            +
                        if project_instructions_path.exists():
         | 
| 1121 | 
            +
                            instructions_path = project_instructions_path
         | 
| 1122 | 
            +
                            instructions_source = "PROJECT"
         | 
| 1123 | 
            +
                            self.logger.info(f"Found project-specific INSTRUCTIONS.md: {instructions_path}")
         | 
| 1124 | 
            +
                        else:
         | 
| 1125 | 
            +
                            # Fall back to framework instructions
         | 
| 1126 | 
            +
                            module_path = Path(__file__).parent.parent
         | 
| 1127 | 
            +
                            framework_instructions_path = module_path / "agents" / "INSTRUCTIONS.md"
         | 
| 1128 | 
            +
                            
         | 
| 1129 | 
            +
                            if framework_instructions_path.exists():
         | 
| 1130 | 
            +
                                instructions_path = framework_instructions_path
         | 
| 1131 | 
            +
                                instructions_source = "FRAMEWORK"
         | 
| 1132 | 
            +
                                self.logger.info(f"Using framework INSTRUCTIONS.md: {instructions_path}")
         | 
| 1133 | 
            +
                            else:
         | 
| 1134 | 
            +
                                self.logger.warning(f"No INSTRUCTIONS.md found in project or framework")
         | 
| 1135 | 
            +
                                return None
         | 
| 898 1136 |  | 
| 899 1137 | 
             
                        # Read raw instructions
         | 
| 900 1138 | 
             
                        raw_instructions = instructions_path.read_text()
         | 
| @@ -904,13 +1142,15 @@ class ClaudeRunner: | |
| 904 1142 | 
             
                            from claude_mpm.services.framework_claude_md_generator.content_assembler import ContentAssembler
         | 
| 905 1143 | 
             
                            assembler = ContentAssembler()
         | 
| 906 1144 | 
             
                            processed_instructions = assembler.apply_template_variables(raw_instructions)
         | 
| 907 | 
            -
                            self.logger.info("Loaded and processed PM  | 
| 1145 | 
            +
                            self.logger.info(f"Loaded and processed {instructions_source} PM instructions with dynamic capabilities")
         | 
| 908 1146 | 
             
                            return processed_instructions
         | 
| 909 1147 | 
             
                        except ImportError:
         | 
| 910 1148 | 
             
                            self.logger.warning("ContentAssembler not available, using raw instructions")
         | 
| 1149 | 
            +
                            self.logger.info(f"Loaded {instructions_source} PM instructions (raw)")
         | 
| 911 1150 | 
             
                            return raw_instructions
         | 
| 912 1151 | 
             
                        except Exception as e:
         | 
| 913 1152 | 
             
                            self.logger.warning(f"Failed to process template variables: {e}, using raw instructions")
         | 
| 1153 | 
            +
                            self.logger.info(f"Loaded {instructions_source} PM instructions (raw, processing failed)")
         | 
| 914 1154 | 
             
                            return raw_instructions
         | 
| 915 1155 |  | 
| 916 1156 | 
             
                    except Exception as e:
         | 
| @@ -1185,6 +1425,10 @@ class ClaudeRunner: | |
| 1185 1425 | 
             
                    import tty
         | 
| 1186 1426 | 
             
                    import signal
         | 
| 1187 1427 |  | 
| 1428 | 
            +
                    # Collect output for response logging if enabled
         | 
| 1429 | 
            +
                    collected_output = [] if self.response_logger else None
         | 
| 1430 | 
            +
                    collected_input = [] if self.response_logger else None
         | 
| 1431 | 
            +
                    
         | 
| 1188 1432 | 
             
                    # Save original terminal settings
         | 
| 1189 1433 | 
             
                    original_tty = None
         | 
| 1190 1434 | 
             
                    if sys.stdin.isatty():
         | 
| @@ -1247,6 +1491,13 @@ class ClaudeRunner: | |
| 1247 1491 | 
             
                                    data = os.read(master_fd, 4096)
         | 
| 1248 1492 | 
             
                                    if data:
         | 
| 1249 1493 | 
             
                                        os.write(sys.stdout.fileno(), data)
         | 
| 1494 | 
            +
                                        # Collect output for response logging
         | 
| 1495 | 
            +
                                        if collected_output is not None:
         | 
| 1496 | 
            +
                                            try:
         | 
| 1497 | 
            +
                                                output_text = data.decode('utf-8', errors='replace')
         | 
| 1498 | 
            +
                                                collected_output.append(output_text)
         | 
| 1499 | 
            +
                                            except Exception:
         | 
| 1500 | 
            +
                                                pass
         | 
| 1250 1501 | 
             
                                        # Broadcast output to WebSocket clients
         | 
| 1251 1502 | 
             
                                        if self.websocket_server:
         | 
| 1252 1503 | 
             
                                            try:
         | 
| @@ -1265,12 +1516,38 @@ class ClaudeRunner: | |
| 1265 1516 | 
             
                                    data = os.read(sys.stdin.fileno(), 4096)
         | 
| 1266 1517 | 
             
                                    if data:
         | 
| 1267 1518 | 
             
                                        os.write(master_fd, data)
         | 
| 1519 | 
            +
                                        # Collect input for response logging
         | 
| 1520 | 
            +
                                        if collected_input is not None:
         | 
| 1521 | 
            +
                                            try:
         | 
| 1522 | 
            +
                                                input_text = data.decode('utf-8', errors='replace')
         | 
| 1523 | 
            +
                                                collected_input.append(input_text)
         | 
| 1524 | 
            +
                                            except Exception:
         | 
| 1525 | 
            +
                                                pass
         | 
| 1268 1526 | 
             
                                except OSError:
         | 
| 1269 1527 | 
             
                                    break
         | 
| 1270 1528 |  | 
| 1271 1529 | 
             
                        # Wait for process to complete
         | 
| 1272 1530 | 
             
                        process.wait()
         | 
| 1273 1531 |  | 
| 1532 | 
            +
                        # Log the interactive session if response logging is enabled
         | 
| 1533 | 
            +
                        if self.response_logger and collected_output is not None and collected_output:
         | 
| 1534 | 
            +
                            try:
         | 
| 1535 | 
            +
                                full_output = ''.join(collected_output)
         | 
| 1536 | 
            +
                                full_input = ''.join(collected_input) if collected_input else "Interactive session"
         | 
| 1537 | 
            +
                                self.response_logger.log_response(
         | 
| 1538 | 
            +
                                    request_summary=f"Interactive session: {full_input[:200]}..." if len(full_input) > 200 else f"Interactive session: {full_input}",
         | 
| 1539 | 
            +
                                    response_content=full_output,
         | 
| 1540 | 
            +
                                    metadata={
         | 
| 1541 | 
            +
                                        "mode": "interactive-subprocess",
         | 
| 1542 | 
            +
                                        "model": "opus",
         | 
| 1543 | 
            +
                                        "exit_code": process.returncode,
         | 
| 1544 | 
            +
                                        "session_type": "subprocess"
         | 
| 1545 | 
            +
                                    },
         | 
| 1546 | 
            +
                                    agent="claude-interactive"
         | 
| 1547 | 
            +
                                )
         | 
| 1548 | 
            +
                            except Exception as e:
         | 
| 1549 | 
            +
                                self.logger.debug(f"Failed to log interactive session: {e}")
         | 
| 1550 | 
            +
                        
         | 
| 1274 1551 | 
             
                        if self.project_logger:
         | 
| 1275 1552 | 
             
                            self.project_logger.log_system(
         | 
| 1276 1553 | 
             
                                f"Claude subprocess exited with code {process.returncode}",
         | 
    
        claude_mpm/core/config_paths.py
    CHANGED
    
    
    
        claude_mpm/core/factories.py
    CHANGED
    
    | @@ -57,14 +57,20 @@ class AgentServiceFactory(ServiceFactory): | |
| 57 57 | 
             
                    config = container.resolve(Config)
         | 
| 58 58 |  | 
| 59 59 | 
             
                    # Get directories from config if not provided
         | 
| 60 | 
            +
                    import os
         | 
| 61 | 
            +
                    
         | 
| 60 62 | 
             
                    if framework_dir is None:
         | 
| 61 63 | 
             
                        framework_dir = Path(config.get('framework.dir', 'framework'))
         | 
| 62 64 |  | 
| 63 65 | 
             
                    if project_dir is None:
         | 
| 64 | 
            -
                         | 
| 66 | 
            +
                        # Check for user working directory from environment
         | 
| 67 | 
            +
                        if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 68 | 
            +
                            project_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 69 | 
            +
                        else:
         | 
| 70 | 
            +
                            project_dir = Path(config.get('project.dir', '.'))
         | 
| 65 71 |  | 
| 66 | 
            -
                    # Create service with  | 
| 67 | 
            -
                    service = AgentDeploymentService()
         | 
| 72 | 
            +
                    # Create service with proper working directory
         | 
| 73 | 
            +
                    service = AgentDeploymentService(working_directory=project_dir)
         | 
| 68 74 |  | 
| 69 75 | 
             
                    # Inject any required dependencies
         | 
| 70 76 | 
             
                    if hasattr(service, 'set_directories'):
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # Agent Memory System
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ## Purpose
         | 
| 4 | 
            +
            Each agent maintains project-specific knowledge in these files. Agents read their memory file before tasks and update it when they learn something new.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            ## Manual Editing
         | 
| 7 | 
            +
            Feel free to edit these files to:
         | 
| 8 | 
            +
            - Add project-specific guidelines
         | 
| 9 | 
            +
            - Remove outdated information  
         | 
| 10 | 
            +
            - Reorganize for better clarity
         | 
| 11 | 
            +
            - Add domain-specific knowledge
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ## Memory Limits
         | 
| 14 | 
            +
            - Max file size: 8KB (~2000 tokens)
         | 
| 15 | 
            +
            - Max sections: 10
         | 
| 16 | 
            +
            - Max items per section: 15
         | 
| 17 | 
            +
            - Files auto-truncate when limits exceeded
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            ## File Format
         | 
| 20 | 
            +
            Standard markdown with structured sections. Agents expect:
         | 
| 21 | 
            +
            - Project Architecture
         | 
| 22 | 
            +
            - Implementation Guidelines
         | 
| 23 | 
            +
            - Common Mistakes to Avoid
         | 
| 24 | 
            +
            - Current Technical Context
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            ## How It Works
         | 
| 27 | 
            +
            1. Agents read their memory file before starting tasks
         | 
| 28 | 
            +
            2. Agents add learnings during or after task completion
         | 
| 29 | 
            +
            3. Files automatically enforce size limits
         | 
| 30 | 
            +
            4. Developers can manually edit for accuracy
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            ## Memory File Lifecycle
         | 
| 33 | 
            +
            - Created automatically when agent first runs
         | 
| 34 | 
            +
            - Updated through hook system after delegations
         | 
| 35 | 
            +
            - Manually editable by developers
         | 
| 36 | 
            +
            - Version controlled with project
         | 
| @@ -0,0 +1,121 @@ | |
| 1 | 
            +
            # Claude MPM Web Dashboard
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This directory contains the modular web dashboard for Claude MPM monitoring and management.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Structure
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ```
         | 
| 8 | 
            +
            src/claude_mpm/dashboard/
         | 
| 9 | 
            +
            ├── static/
         | 
| 10 | 
            +
            │   ├── css/
         | 
| 11 | 
            +
            │   │   └── dashboard.css          # Main stylesheet
         | 
| 12 | 
            +
            │   └── js/
         | 
| 13 | 
            +
            │       ├── socket-client.js       # Socket.IO connection management
         | 
| 14 | 
            +
            │       ├── dashboard.js          # Main dashboard application
         | 
| 15 | 
            +
            │       └── components/
         | 
| 16 | 
            +
            │           ├── event-viewer.js    # Event display and filtering
         | 
| 17 | 
            +
            │           ├── module-viewer.js   # Event detail viewer
         | 
| 18 | 
            +
            │           └── session-manager.js # Session management
         | 
| 19 | 
            +
            ├── templates/
         | 
| 20 | 
            +
            │   └── index.html               # Main dashboard HTML
         | 
| 21 | 
            +
            ├── index.html                   # Root index with redirect
         | 
| 22 | 
            +
            ├── test_dashboard.html          # Test version for verification
         | 
| 23 | 
            +
            └── README.md                    # This file
         | 
| 24 | 
            +
            ```
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            ## Components
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            ### SocketClient (`socket-client.js`)
         | 
| 29 | 
            +
            - Manages WebSocket connections to the Claude MPM server
         | 
| 30 | 
            +
            - Handles event reception and processing
         | 
| 31 | 
            +
            - Provides callbacks for connection state changes
         | 
| 32 | 
            +
            - Maintains event history and session tracking
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            ### EventViewer (`components/event-viewer.js`)
         | 
| 35 | 
            +
            - Displays events in a filterable list
         | 
| 36 | 
            +
            - Supports search, type filtering, and session filtering
         | 
| 37 | 
            +
            - Handles event selection and keyboard navigation
         | 
| 38 | 
            +
            - Updates metrics display (total events, events per minute, etc.)
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            ### ModuleViewer (`components/module-viewer.js`)
         | 
| 41 | 
            +
            - Shows detailed information about selected events
         | 
| 42 | 
            +
            - Provides structured views for different event types
         | 
| 43 | 
            +
            - Displays raw JSON data for debugging
         | 
| 44 | 
            +
            - Organizes events by class/category
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            ### SessionManager (`components/session-manager.js`)
         | 
| 47 | 
            +
            - Manages session selection and filtering
         | 
| 48 | 
            +
            - Updates session dropdown with available sessions
         | 
| 49 | 
            +
            - Tracks current active session
         | 
| 50 | 
            +
            - Updates footer information
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            ### Dashboard (`dashboard.js`)
         | 
| 53 | 
            +
            - Main application coordinator
         | 
| 54 | 
            +
            - Handles tab switching between Events, Agents, Tools, and Files
         | 
| 55 | 
            +
            - Manages component interactions
         | 
| 56 | 
            +
            - Provides global functions for backward compatibility
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            ## Features
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            ### File-Centric Files Tab
         | 
| 61 | 
            +
            The Files tab now shows a file-centric view where:
         | 
| 62 | 
            +
            - Each file appears only once in the list
         | 
| 63 | 
            +
            - Files show read/write icons based on operations performed
         | 
| 64 | 
            +
            - Most recently accessed files appear at the bottom
         | 
| 65 | 
            +
            - Clicking a file shows all operations performed on it with timestamps and agent information
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            ### Tab Organization
         | 
| 68 | 
            +
            - **Events**: All events with filtering and search
         | 
| 69 | 
            +
            - **Agents**: Agent-specific events and operations
         | 
| 70 | 
            +
            - **Tools**: Tool usage and parameters
         | 
| 71 | 
            +
            - **Files**: File operations organized by file path
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            ### Enhanced Event Details
         | 
| 74 | 
            +
            - Structured views for different event types
         | 
| 75 | 
            +
            - Todo checklists with status indicators
         | 
| 76 | 
            +
            - Agent and tool information
         | 
| 77 | 
            +
            - Session tracking and filtering
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            ## Usage
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            ### Development
         | 
| 82 | 
            +
            For development and testing, use `test_dashboard.html` which includes module verification.
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            ### Production
         | 
| 85 | 
            +
            Use `templates/dashboard.html` as the main template, ensuring all static files are served correctly.
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            ### Integration
         | 
| 88 | 
            +
            The dashboard can be integrated into a Flask/FastAPI application by serving the static files and using the template.
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            ## Migration from Original
         | 
| 91 | 
            +
             | 
| 92 | 
            +
            The original monolithic `claude_mpm_socketio_dashboard.html` has been replaced with a modular structure:
         | 
| 93 | 
            +
             | 
| 94 | 
            +
            1. **CSS**: Extracted to `static/css/dashboard.css`
         | 
| 95 | 
            +
            2. **JavaScript**: Split into logical modules in `static/js/`
         | 
| 96 | 
            +
            3. **HTML**: Clean template in `templates/dashboard.html`
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            All original functionality has been preserved while improving:
         | 
| 99 | 
            +
            - Code organization and maintainability
         | 
| 100 | 
            +
            - Module reusability
         | 
| 101 | 
            +
            - Easier debugging and development
         | 
| 102 | 
            +
            - Better separation of concerns
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            ## Backward Compatibility
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            Global functions are maintained for compatibility:
         | 
| 107 | 
            +
            - `connectSocket()`
         | 
| 108 | 
            +
            - `disconnectSocket()`
         | 
| 109 | 
            +
            - `clearEvents()`
         | 
| 110 | 
            +
            - `exportEvents()`
         | 
| 111 | 
            +
            - `clearSelection()`
         | 
| 112 | 
            +
            - `switchTab(tabName)`
         | 
| 113 | 
            +
             | 
| 114 | 
            +
            ## Browser Support
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            Requires modern browsers with support for:
         | 
| 117 | 
            +
            - ES6 Classes
         | 
| 118 | 
            +
            - Fetch API
         | 
| 119 | 
            +
            - Custom Events
         | 
| 120 | 
            +
            - CSS Grid/Flexbox
         | 
| 121 | 
            +
            - Socket.IO 4.x
         |