claude-mpm 4.0.19__py3-none-any.whl → 4.0.22__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 (61) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/__main__.py +4 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
  5. claude_mpm/agents/INSTRUCTIONS.md +74 -0
  6. claude_mpm/agents/OUTPUT_STYLE.md +84 -0
  7. claude_mpm/agents/WORKFLOW.md +308 -4
  8. claude_mpm/agents/agents_metadata.py +52 -0
  9. claude_mpm/agents/base_agent_loader.py +75 -19
  10. claude_mpm/agents/templates/__init__.py +4 -0
  11. claude_mpm/agents/templates/api_qa.json +206 -0
  12. claude_mpm/agents/templates/qa.json +1 -1
  13. claude_mpm/agents/templates/research.json +24 -16
  14. claude_mpm/agents/templates/ticketing.json +18 -5
  15. claude_mpm/agents/templates/vercel_ops_agent.json +281 -0
  16. claude_mpm/agents/templates/vercel_ops_instructions.md +582 -0
  17. claude_mpm/cli/__init__.py +23 -1
  18. claude_mpm/cli/__main__.py +4 -0
  19. claude_mpm/cli/commands/mcp_command_router.py +87 -1
  20. claude_mpm/cli/commands/mcp_install_commands.py +207 -26
  21. claude_mpm/cli/commands/memory.py +32 -5
  22. claude_mpm/cli/commands/run.py +33 -6
  23. claude_mpm/cli/parsers/base_parser.py +5 -0
  24. claude_mpm/cli/parsers/mcp_parser.py +23 -0
  25. claude_mpm/cli/parsers/run_parser.py +5 -0
  26. claude_mpm/cli/utils.py +17 -4
  27. claude_mpm/constants.py +1 -0
  28. claude_mpm/core/base_service.py +8 -2
  29. claude_mpm/core/config.py +122 -32
  30. claude_mpm/core/framework_loader.py +385 -34
  31. claude_mpm/core/interactive_session.py +77 -12
  32. claude_mpm/core/oneshot_session.py +7 -1
  33. claude_mpm/core/output_style_manager.py +468 -0
  34. claude_mpm/core/unified_paths.py +190 -21
  35. claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
  36. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
  37. claude_mpm/init.py +1 -0
  38. claude_mpm/scripts/socketio_daemon.py +67 -7
  39. claude_mpm/scripts/socketio_daemon_hardened.py +897 -0
  40. claude_mpm/services/agents/deployment/agent_deployment.py +216 -10
  41. claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
  42. claude_mpm/services/agents/deployment/async_agent_deployment.py +65 -1
  43. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
  44. claude_mpm/services/agents/memory/__init__.py +0 -2
  45. claude_mpm/services/agents/memory/agent_memory_manager.py +577 -44
  46. claude_mpm/services/agents/memory/content_manager.py +144 -14
  47. claude_mpm/services/agents/memory/template_generator.py +7 -354
  48. claude_mpm/services/mcp_gateway/server/stdio_server.py +61 -169
  49. claude_mpm/services/memory_hook_service.py +62 -4
  50. claude_mpm/services/runner_configuration_service.py +5 -9
  51. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  52. claude_mpm/services/socketio/server/core.py +4 -0
  53. claude_mpm/services/socketio/server/main.py +23 -4
  54. claude_mpm/services/subprocess_launcher_service.py +5 -0
  55. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/METADATA +1 -1
  56. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/RECORD +60 -54
  57. claude_mpm/services/agents/memory/analyzer.py +0 -430
  58. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/WHEEL +0 -0
  59. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/entry_points.txt +0 -0
  60. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/licenses/LICENSE +0 -0
  61. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/top_level.txt +0 -0
@@ -58,6 +58,64 @@ class FrameworkLoader:
58
58
 
59
59
  # Initialize agent registry
60
60
  self.agent_registry = AgentRegistryAdapter(self.framework_path)
61
+
62
+ # Initialize output style manager (must be after content is loaded)
63
+ self.output_style_manager = None
64
+ # Defer initialization until first use to ensure content is loaded
65
+
66
+ def _initialize_output_style(self) -> None:
67
+ """Initialize output style management and deploy if applicable."""
68
+ try:
69
+ from claude_mpm.core.output_style_manager import OutputStyleManager
70
+
71
+ self.output_style_manager = OutputStyleManager()
72
+
73
+ # Log detailed output style status
74
+ self._log_output_style_status()
75
+
76
+ # Extract and save output style content (pass self to reuse loaded content)
77
+ output_style_content = self.output_style_manager.extract_output_style_content(framework_loader=self)
78
+ output_style_path = self.output_style_manager.save_output_style(output_style_content)
79
+
80
+ # Deploy to Claude Desktop if supported
81
+ deployed = self.output_style_manager.deploy_output_style(output_style_content)
82
+
83
+ if deployed:
84
+ self.logger.info("✅ Output style deployed to Claude Desktop >= 1.0.83")
85
+ else:
86
+ self.logger.info("📝 Output style will be injected into instructions for older Claude versions")
87
+
88
+ except Exception as e:
89
+ self.logger.warning(f"❌ Failed to initialize output style manager: {e}")
90
+ # Continue without output style management
91
+
92
+ def _log_output_style_status(self) -> None:
93
+ """Log comprehensive output style status information."""
94
+ if not self.output_style_manager:
95
+ return
96
+
97
+ # Claude version detection
98
+ claude_version = self.output_style_manager.claude_version
99
+ if claude_version:
100
+ self.logger.info(f"Claude Desktop version detected: {claude_version}")
101
+
102
+ # Check if version supports output styles
103
+ if self.output_style_manager.supports_output_styles():
104
+ self.logger.info("✅ Claude Desktop supports output styles (>= 1.0.83)")
105
+
106
+ # Check deployment status
107
+ output_style_path = self.output_style_manager.output_style_path
108
+ if output_style_path.exists():
109
+ self.logger.info(f"📁 Output style file exists: {output_style_path}")
110
+ else:
111
+ self.logger.info(f"📝 Output style will be created at: {output_style_path}")
112
+
113
+ else:
114
+ self.logger.info(f"⚠️ Claude Desktop {claude_version} does not support output styles (< 1.0.83)")
115
+ self.logger.info("📝 Output style content will be injected into framework instructions")
116
+ else:
117
+ self.logger.info("⚠️ Claude Desktop not detected or version unknown")
118
+ self.logger.info("📝 Output style content will be injected into framework instructions as fallback")
61
119
 
62
120
  def _detect_framework_path(self) -> Optional[Path]:
63
121
  """Auto-detect claude-mpm framework using unified path management."""
@@ -228,6 +286,29 @@ class FrameworkLoader:
228
286
  self.logger.error(f"Failed to load {file_type}: {e}")
229
287
  return None
230
288
 
289
+ def _migrate_memory_file(self, old_path: Path, new_path: Path) -> None:
290
+ """
291
+ Migrate memory file from old naming convention to new.
292
+
293
+ WHY: Supports backward compatibility by automatically migrating from
294
+ the old {agent_id}_agent.md and {agent_id}.md formats to the new {agent_id}_memories.md format.
295
+
296
+ Args:
297
+ old_path: Path to the old file
298
+ new_path: Path to the new file
299
+ """
300
+ if old_path.exists() and not new_path.exists():
301
+ try:
302
+ # Read content from old file
303
+ content = old_path.read_text(encoding="utf-8")
304
+ # Write to new file
305
+ new_path.write_text(content, encoding="utf-8")
306
+ # Remove old file
307
+ old_path.unlink()
308
+ self.logger.info(f"Migrated memory file from {old_path.name} to {new_path.name}")
309
+ except Exception as e:
310
+ self.logger.error(f"Failed to migrate memory file {old_path.name}: {e}")
311
+
231
312
  def _load_instructions_file(self, content: Dict[str, Any]) -> None:
232
313
  """
233
314
  Load INSTRUCTIONS.md or legacy CLAUDE.md from working directory.
@@ -344,23 +425,22 @@ class FrameworkLoader:
344
425
 
345
426
  def _load_actual_memories(self, content: Dict[str, Any]) -> None:
346
427
  """
347
- Load actual memories from .claude-mpm/memories/ directory.
428
+ Load actual memories from both user and project directories.
429
+
430
+ Loading order:
431
+ 1. User-level memories from ~/.claude-mpm/memories/ (global defaults)
432
+ 2. Project-level memories from ./.claude-mpm/memories/ (overrides user)
348
433
 
349
434
  This loads:
350
- 1. PM memories from PM.md (always loaded)
351
- 2. Agent memories from <agent>.md (only if agent is deployed)
435
+ 1. PM memories from PM_memories.md (always loaded)
436
+ 2. Agent memories from <agent>_memories.md (only if agent is deployed)
352
437
 
353
438
  Args:
354
439
  content: Dictionary to update with actual memories
355
440
  """
356
- memories_dir = Path.cwd() / ".claude-mpm" / "memories"
357
-
358
- # Log the memory loading process
359
- if memories_dir.exists():
360
- self.logger.info(f"Loading memory files from: {memories_dir}")
361
- else:
362
- self.logger.debug(f"No memories directory found at: {memories_dir}")
363
- return
441
+ # Define memory directories in priority order (user first, then project)
442
+ user_memories_dir = Path.home() / ".claude-mpm" / "memories"
443
+ project_memories_dir = Path.cwd() / ".claude-mpm" / "memories"
364
444
 
365
445
  # Check for deployed agents
366
446
  deployed_agents = self._get_deployed_agents()
@@ -369,49 +449,286 @@ class FrameworkLoader:
369
449
  loaded_count = 0
370
450
  skipped_count = 0
371
451
 
452
+ # Dictionary to store aggregated memories
453
+ pm_memories = []
454
+ agent_memories_dict = {}
455
+
456
+ # Load memories from user directory first
457
+ if user_memories_dir.exists():
458
+ self.logger.info(f"Loading user-level memory files from: {user_memories_dir}")
459
+ loaded, skipped = self._load_memories_from_directory(
460
+ user_memories_dir, deployed_agents, pm_memories, agent_memories_dict, "user"
461
+ )
462
+ loaded_count += loaded
463
+ skipped_count += skipped
464
+ else:
465
+ self.logger.debug(f"No user memories directory found at: {user_memories_dir}")
466
+
467
+ # Load memories from project directory (overrides user memories)
468
+ if project_memories_dir.exists():
469
+ self.logger.info(f"Loading project-level memory files from: {project_memories_dir}")
470
+ loaded, skipped = self._load_memories_from_directory(
471
+ project_memories_dir, deployed_agents, pm_memories, agent_memories_dict, "project"
472
+ )
473
+ loaded_count += loaded
474
+ skipped_count += skipped
475
+ else:
476
+ self.logger.debug(f"No project memories directory found at: {project_memories_dir}")
477
+
478
+ # Aggregate PM memories
479
+ if pm_memories:
480
+ aggregated_pm = self._aggregate_memories(pm_memories)
481
+ content["actual_memories"] = aggregated_pm
482
+ memory_size = len(aggregated_pm.encode('utf-8'))
483
+ self.logger.info(f"Aggregated PM memory ({memory_size:,} bytes) from {len(pm_memories)} source(s)")
484
+
485
+ # Store agent memories (already aggregated per agent)
486
+ if agent_memories_dict:
487
+ content["agent_memories"] = agent_memories_dict
488
+ for agent_name, memory_content in agent_memories_dict.items():
489
+ memory_size = len(memory_content.encode('utf-8'))
490
+ self.logger.debug(f"Aggregated {agent_name} memory: {memory_size:,} bytes")
491
+
492
+ # Log detailed summary
493
+ if loaded_count > 0 or skipped_count > 0:
494
+ # Count unique agents with memories
495
+ agent_count = len(agent_memories_dict) if agent_memories_dict else 0
496
+ pm_loaded = bool(content.get("actual_memories"))
497
+
498
+ summary_parts = []
499
+ if pm_loaded:
500
+ summary_parts.append("PM memory loaded")
501
+ if agent_count > 0:
502
+ summary_parts.append(f"{agent_count} agent memories loaded")
503
+ if skipped_count > 0:
504
+ summary_parts.append(f"{skipped_count} non-deployed agent memories skipped")
505
+
506
+ self.logger.info(f"Memory loading complete: {' | '.join(summary_parts)}")
507
+
508
+ # Log deployed agents for reference
509
+ if len(deployed_agents) > 0:
510
+ self.logger.debug(f"Deployed agents available for memory loading: {', '.join(sorted(deployed_agents))}")
511
+
512
+ def _load_memories_from_directory(
513
+ self,
514
+ memories_dir: Path,
515
+ deployed_agents: set,
516
+ pm_memories: list,
517
+ agent_memories_dict: dict,
518
+ source: str
519
+ ) -> tuple[int, int]:
520
+ """
521
+ Load memories from a specific directory.
522
+
523
+ Args:
524
+ memories_dir: Directory to load memories from
525
+ deployed_agents: Set of deployed agent names
526
+ pm_memories: List to append PM memories to
527
+ agent_memories_dict: Dict to store agent memories
528
+ source: Source label ("user" or "project")
529
+
530
+ Returns:
531
+ Tuple of (loaded_count, skipped_count)
532
+ """
533
+ loaded_count = 0
534
+ skipped_count = 0
535
+
372
536
  # Load PM memories (always loaded)
373
- pm_memory_path = memories_dir / "PM.md"
537
+ # Support migration from both old formats
538
+ pm_memory_path = memories_dir / "PM_memories.md"
539
+ old_pm_path = memories_dir / "PM.md"
540
+
541
+ # Migrate from old PM.md if needed
542
+ if not pm_memory_path.exists() and old_pm_path.exists():
543
+ try:
544
+ old_pm_path.rename(pm_memory_path)
545
+ self.logger.info(f"Migrated PM.md to PM_memories.md")
546
+ except Exception as e:
547
+ self.logger.error(f"Failed to migrate PM.md: {e}")
548
+ pm_memory_path = old_pm_path # Fall back to old path
374
549
  if pm_memory_path.exists():
375
550
  loaded_content = self._try_load_file(
376
- pm_memory_path, "PM memory"
551
+ pm_memory_path, f"PM memory ({source})"
377
552
  )
378
553
  if loaded_content:
379
- content["actual_memories"] = loaded_content
554
+ pm_memories.append({
555
+ "source": source,
556
+ "content": loaded_content,
557
+ "path": pm_memory_path
558
+ })
380
559
  memory_size = len(loaded_content.encode('utf-8'))
381
- self.logger.info(f"Loaded PM memory: {pm_memory_path} ({memory_size:,} bytes)")
560
+ self.logger.info(f"Loaded {source} PM memory: {pm_memory_path} ({memory_size:,} bytes)")
382
561
  loaded_count += 1
383
- else:
384
- self.logger.debug(f"No PM memory found at: {pm_memory_path}")
562
+
563
+ # First, migrate any old format memory files to new format
564
+ # This handles backward compatibility for existing installations
565
+ for old_file in memories_dir.glob("*.md"):
566
+ # Skip files already in correct format and special files
567
+ if old_file.name.endswith("_memories.md") or old_file.name in ["PM.md", "README.md"]:
568
+ continue
569
+
570
+ # Determine new name based on old format
571
+ if old_file.stem.endswith("_agent"):
572
+ # Old format: {agent_name}_agent.md -> {agent_name}_memories.md
573
+ agent_name = old_file.stem[:-6] # Remove "_agent" suffix
574
+ new_path = memories_dir / f"{agent_name}_memories.md"
575
+ if not new_path.exists():
576
+ self._migrate_memory_file(old_file, new_path)
577
+ else:
578
+ # Intermediate format: {agent_name}.md -> {agent_name}_memories.md
579
+ agent_name = old_file.stem
580
+ new_path = memories_dir / f"{agent_name}_memories.md"
581
+ if not new_path.exists():
582
+ self._migrate_memory_file(old_file, new_path)
385
583
 
386
584
  # Load agent memories (only for deployed agents)
387
- for memory_file in memories_dir.glob("*.md"):
388
- # Skip PM.md as we already handled it
389
- if memory_file.name == "PM.md":
585
+ # Only process *_memories.md files to avoid README.md and other docs
586
+ for memory_file in memories_dir.glob("*_memories.md"):
587
+ # Skip PM_memories.md as we already handled it
588
+ if memory_file.name == "PM_memories.md":
390
589
  continue
391
590
 
392
- # Extract agent name from file
393
- agent_name = memory_file.stem
591
+ # Extract agent name from file (remove "_memories" suffix)
592
+ agent_name = memory_file.stem[:-9] # Remove "_memories" suffix
394
593
 
395
594
  # Check if agent is deployed
396
595
  if agent_name in deployed_agents:
397
596
  loaded_content = self._try_load_file(
398
- memory_file, f"agent memory: {agent_name}"
597
+ memory_file, f"agent memory: {agent_name} ({source})"
399
598
  )
400
599
  if loaded_content:
401
- # Store agent memories separately (for future use)
402
- if "agent_memories" not in content:
403
- content["agent_memories"] = {}
404
- content["agent_memories"][agent_name] = loaded_content
600
+ # Store or merge agent memories
601
+ if agent_name not in agent_memories_dict:
602
+ agent_memories_dict[agent_name] = []
603
+
604
+ # If it's a list, append the new memory entry
605
+ if isinstance(agent_memories_dict[agent_name], list):
606
+ agent_memories_dict[agent_name].append({
607
+ "source": source,
608
+ "content": loaded_content,
609
+ "path": memory_file
610
+ })
611
+
405
612
  memory_size = len(loaded_content.encode('utf-8'))
406
- self.logger.info(f"Loaded agent memory: {memory_file.name} (deployed agent found, {memory_size:,} bytes)")
613
+ self.logger.info(f"Loaded {source} memory for {agent_name}: {memory_file.name} ({memory_size:,} bytes)")
407
614
  loaded_count += 1
408
615
  else:
409
- self.logger.info(f"Skipped agent memory: {memory_file.name} (agent not deployed)")
616
+ # Provide more detailed logging about why the memory was skipped
617
+ self.logger.info(f"Skipped {source} memory: {memory_file.name} (agent '{agent_name}' not deployed)")
618
+ # Also log a debug message with available agents for diagnostics
619
+ if agent_name.replace('_', '-') in deployed_agents or agent_name.replace('-', '_') in deployed_agents:
620
+ # Detect naming mismatches
621
+ alt_name = agent_name.replace('_', '-') if '_' in agent_name else agent_name.replace('-', '_')
622
+ if alt_name in deployed_agents:
623
+ self.logger.warning(
624
+ f"Naming mismatch detected: Memory file uses '{agent_name}' but deployed agent is '{alt_name}'. "
625
+ f"Consider renaming {memory_file.name} to {alt_name}_memories.md"
626
+ )
410
627
  skipped_count += 1
411
628
 
412
- # Log summary
413
- if loaded_count > 0 or skipped_count > 0:
414
- self.logger.info(f"Memory loading complete: {loaded_count} memories loaded, {skipped_count} skipped")
629
+ # After loading all memories for this directory, aggregate agent memories
630
+ for agent_name in list(agent_memories_dict.keys()):
631
+ if isinstance(agent_memories_dict[agent_name], list) and agent_memories_dict[agent_name]:
632
+ # Aggregate memories for this agent
633
+ aggregated = self._aggregate_memories(agent_memories_dict[agent_name])
634
+ agent_memories_dict[agent_name] = aggregated
635
+
636
+ return loaded_count, skipped_count
637
+
638
+ def _aggregate_memories(self, memory_entries: list) -> str:
639
+ """
640
+ Aggregate multiple memory entries into a single memory string.
641
+
642
+ Strategy:
643
+ - Parse memories by sections
644
+ - Merge sections, with project-level taking precedence
645
+ - Remove exact duplicates within sections
646
+ - Preserve unique entries from both sources
647
+
648
+ Args:
649
+ memory_entries: List of memory entries with source, content, and path
650
+
651
+ Returns:
652
+ Aggregated memory content as a string
653
+ """
654
+ if not memory_entries:
655
+ return ""
656
+
657
+ # If only one entry, return it as-is
658
+ if len(memory_entries) == 1:
659
+ return memory_entries[0]["content"]
660
+
661
+ # Parse all memories into sections
662
+ all_sections = {}
663
+ metadata_lines = []
664
+
665
+ for entry in memory_entries:
666
+ content = entry["content"]
667
+ source = entry["source"]
668
+
669
+ # Parse content into sections
670
+ current_section = None
671
+ current_items = []
672
+
673
+ for line in content.split('\n'):
674
+ # Check for metadata lines
675
+ if line.startswith('<!-- ') and line.endswith(' -->'):
676
+ # Only keep metadata from project source or if not already present
677
+ if source == "project" or line not in metadata_lines:
678
+ metadata_lines.append(line)
679
+ # Check for section headers (## Level 2 headers)
680
+ elif line.startswith('## '):
681
+ # Save previous section if exists
682
+ if current_section and current_items:
683
+ if current_section not in all_sections:
684
+ all_sections[current_section] = {}
685
+ # Store items with their source
686
+ for item in current_items:
687
+ # Use content as key to detect duplicates
688
+ all_sections[current_section][item] = source
689
+
690
+ # Start new section
691
+ current_section = line
692
+ current_items = []
693
+ # Check for content lines (skip empty lines)
694
+ elif line.strip() and current_section:
695
+ current_items.append(line)
696
+
697
+ # Save last section
698
+ if current_section and current_items:
699
+ if current_section not in all_sections:
700
+ all_sections[current_section] = {}
701
+ for item in current_items:
702
+ # Project source overrides user source
703
+ if item not in all_sections[current_section] or source == "project":
704
+ all_sections[current_section][item] = source
705
+
706
+ # Build aggregated content
707
+ lines = []
708
+
709
+ # Add metadata
710
+ if metadata_lines:
711
+ lines.extend(metadata_lines)
712
+ lines.append("")
713
+
714
+ # Add header
715
+ lines.append("# Aggregated Memory")
716
+ lines.append("")
717
+ lines.append("*This memory combines user-level and project-level memories.*")
718
+ lines.append("")
719
+
720
+ # Add sections
721
+ for section_header in sorted(all_sections.keys()):
722
+ lines.append(section_header)
723
+ section_items = all_sections[section_header]
724
+
725
+ # Sort items to ensure consistent output
726
+ for item in sorted(section_items.keys()):
727
+ lines.append(item)
728
+
729
+ lines.append("") # Empty line after section
730
+
731
+ return '\n'.join(lines)
415
732
 
416
733
  def _load_single_agent(
417
734
  self, agent_file: Path
@@ -498,7 +815,7 @@ class FrameworkLoader:
498
815
  "project_workflow": "",
499
816
  "memory_instructions": "",
500
817
  "project_memory": "",
501
- "actual_memories": "", # Add field for actual memories from PM.md
818
+ "actual_memories": "", # Add field for actual memories from PM_memories.md
502
819
  }
503
820
 
504
821
  # Load instructions file from working directory
@@ -551,7 +868,7 @@ class FrameworkLoader:
551
868
  # Load MEMORY.md - check for project-specific first, then system
552
869
  self._load_memory_instructions(content)
553
870
 
554
- # Load actual memories from .claude-mpm/memories/PM.md
871
+ # Load actual memories from .claude-mpm/memories/PM_memories.md
555
872
  self._load_actual_memories(content)
556
873
 
557
874
  # Discover agent directories
@@ -742,6 +1059,17 @@ class FrameworkLoader:
742
1059
  """Format full framework instructions."""
743
1060
  from datetime import datetime
744
1061
 
1062
+ # Initialize output style manager on first use (ensures content is loaded)
1063
+ if self.output_style_manager is None:
1064
+ self._initialize_output_style()
1065
+
1066
+ # Check if we need to inject output style content for older Claude versions
1067
+ inject_output_style = False
1068
+ if self.output_style_manager:
1069
+ inject_output_style = self.output_style_manager.should_inject_content()
1070
+ if inject_output_style:
1071
+ self.logger.info("Injecting output style content into instructions for Claude < 1.0.83")
1072
+
745
1073
  # If we have the full framework INSTRUCTIONS.md, use it
746
1074
  if self.framework_content.get("framework_instructions"):
747
1075
  instructions = self._strip_metadata_comments(
@@ -773,6 +1101,20 @@ class FrameworkLoader:
773
1101
  instructions += "**The following are your accumulated memories and knowledge from this project:**\n\n"
774
1102
  instructions += self.framework_content["actual_memories"]
775
1103
  instructions += "\n"
1104
+
1105
+ # Add agent memories if available
1106
+ if self.framework_content.get("agent_memories"):
1107
+ agent_memories = self.framework_content["agent_memories"]
1108
+ if agent_memories:
1109
+ instructions += "\n\n## Agent Memories\n\n"
1110
+ instructions += "**The following are accumulated memories from specialized agents:**\n\n"
1111
+
1112
+ for agent_name in sorted(agent_memories.keys()):
1113
+ memory_content = agent_memories[agent_name]
1114
+ if memory_content:
1115
+ instructions += f"### {agent_name.replace('_', ' ').title()} Agent Memory\n\n"
1116
+ instructions += memory_content
1117
+ instructions += "\n\n"
776
1118
 
777
1119
  # Add dynamic agent capabilities section
778
1120
  instructions += self._generate_agent_capabilities_section()
@@ -789,6 +1131,15 @@ class FrameworkLoader:
789
1131
  self.framework_content["base_pm_instructions"]
790
1132
  )
791
1133
  instructions += f"\n\n{base_pm}"
1134
+
1135
+ # Inject output style content if needed (for Claude < 1.0.83)
1136
+ if inject_output_style and self.output_style_manager:
1137
+ output_style_content = self.output_style_manager.get_injectable_content(framework_loader=self)
1138
+ if output_style_content:
1139
+ instructions += "\n\n## Output Style Configuration\n"
1140
+ instructions += "**Note: The following output style is injected for Claude < 1.0.83**\n\n"
1141
+ instructions += output_style_content
1142
+ instructions += "\n"
792
1143
 
793
1144
  # Clean up any trailing whitespace
794
1145
  instructions = instructions.rstrip() + "\n"
@@ -134,6 +134,7 @@ class InteractiveSession:
134
134
 
135
135
  # Deploy project-specific agents
136
136
  self.runner.deploy_project_agents_to_claude()
137
+
137
138
 
138
139
  # Build command
139
140
  cmd = self._build_claude_command()
@@ -296,33 +297,93 @@ class InteractiveSession:
296
297
  def _display_welcome_message(self) -> None:
297
298
  """Display the interactive session welcome message."""
298
299
  version_str = self.runner._get_version()
300
+
301
+ # Get output style status
302
+ output_style_info = self._get_output_style_info()
299
303
 
300
304
  print("\033[32m╭───────────────────────────────────────────────────╮\033[0m")
301
305
  print(
302
306
  "\033[32m│\033[0m ✻ Claude MPM - Interactive Session \033[32m│\033[0m"
303
307
  )
304
308
  print(f"\033[32m│\033[0m Version {version_str:<40}\033[32m│\033[0m")
309
+ if output_style_info:
310
+ print(f"\033[32m│\033[0m {output_style_info:<49}\033[32m│\033[0m")
305
311
  print("\033[32m│ │\033[0m")
306
312
  print(
307
313
  "\033[32m│\033[0m Type '/agents' to see available agents \033[32m│\033[0m"
308
314
  )
309
315
  print("\033[32m╰───────────────────────────────────────────────────╯\033[0m")
310
316
  print("") # Add blank line after box
317
+
318
+
319
+ def _get_output_style_info(self) -> Optional[str]:
320
+ """Get output style status for display."""
321
+ try:
322
+ # Check if output style manager is available through framework loader
323
+ if hasattr(self.runner, 'framework_loader') and self.runner.framework_loader:
324
+ if hasattr(self.runner.framework_loader, 'output_style_manager'):
325
+ osm = self.runner.framework_loader.output_style_manager
326
+ if osm:
327
+ if osm.claude_version and osm.supports_output_styles():
328
+ # Check if claude-mpm style is active
329
+ settings_file = osm.settings_file
330
+ if settings_file.exists():
331
+ import json
332
+ settings = json.loads(settings_file.read_text())
333
+ active_style = settings.get("activeOutputStyle")
334
+ if active_style == "claude-mpm":
335
+ return "Output Style: claude-mpm ✅"
336
+ else:
337
+ return f"Output Style: {active_style or 'none'}"
338
+ return "Output Style: Available"
339
+ else:
340
+ return "Output Style: Injected (legacy)"
341
+ except Exception:
342
+ pass
343
+ return None
311
344
 
312
345
  def _build_claude_command(self) -> list:
313
346
  """Build the Claude command with all necessary arguments."""
314
- cmd = ["claude", "--model", "opus", "--dangerously-skip-permissions"]
315
-
316
- # Add custom arguments
317
- if self.runner.claude_args:
318
- cmd.extend(self.runner.claude_args)
319
-
320
- # Add system instructions
321
- from claude_mpm.core.claude_runner import create_simple_context
322
-
323
- system_prompt = self.runner._create_system_prompt()
324
- if system_prompt and system_prompt != create_simple_context():
325
- cmd.extend(["--append-system-prompt", system_prompt])
347
+ # Check if --resume flag is present
348
+ has_resume = self.runner.claude_args and "--resume" in self.runner.claude_args
349
+
350
+ if has_resume:
351
+ # When resuming, use minimal command to avoid interfering with conversation selection
352
+ self.logger.info("🔄 Resume mode detected - using minimal Claude command to preserve conversation selection")
353
+ cmd = ["claude"]
354
+
355
+ # Add only the claude_args (which includes --resume)
356
+ if self.runner.claude_args:
357
+ cmd.extend(self.runner.claude_args)
358
+ self.logger.info(f"Resume command: {cmd}")
359
+
360
+ return cmd
361
+ else:
362
+ # Normal mode - full command with all claude-mpm enhancements
363
+ cmd = ["claude", "--model", "opus", "--dangerously-skip-permissions"]
364
+
365
+ # Add custom arguments
366
+ if self.runner.claude_args:
367
+ # Enhanced debug logging for --resume flag verification
368
+ self.logger.debug(f"Raw claude_args received: {self.runner.claude_args}")
369
+ cmd.extend(self.runner.claude_args)
370
+
371
+ # Add system instructions
372
+ from claude_mpm.core.claude_runner import create_simple_context
373
+
374
+ system_prompt = self.runner._create_system_prompt()
375
+ if system_prompt and system_prompt != create_simple_context():
376
+ cmd.extend(["--append-system-prompt", system_prompt])
377
+
378
+ # Final command verification
379
+ # self.logger.info(f"Final Claude command built: {' '.join(cmd)}")
380
+
381
+ # Explicit --resume flag verification
382
+ if "--resume" in cmd:
383
+ self.logger.info("✅ VERIFIED: --resume flag IS included in final command")
384
+ self.logger.debug(f"--resume position in command: {cmd.index('--resume')}")
385
+ else:
386
+ self.logger.debug("ℹ️ --resume flag NOT included in final command")
326
387
 
327
388
  return cmd
328
389
 
@@ -341,6 +402,10 @@ class InteractiveSession:
341
402
  for var in claude_vars_to_remove:
342
403
  clean_env.pop(var, None)
343
404
 
405
+ # Disable telemetry for Claude Code
406
+ # This ensures Claude Code doesn't send telemetry data during runtime
407
+ clean_env["DISABLE_TELEMETRY"] = "1"
408
+
344
409
  return clean_env
345
410
 
346
411
  def _change_to_user_directory(self, env: dict) -> None:
@@ -267,7 +267,13 @@ class OneshotSession:
267
267
 
268
268
  def _prepare_environment(self) -> Dict[str, str]:
269
269
  """Prepare the execution environment."""
270
- return os.environ.copy()
270
+ env = os.environ.copy()
271
+
272
+ # Disable telemetry for Claude Code
273
+ # This ensures Claude Code doesn't send telemetry data during runtime
274
+ env["DISABLE_TELEMETRY"] = "1"
275
+
276
+ return env
271
277
 
272
278
  def _build_command(self) -> list:
273
279
  """Build the base Claude command."""