claude-mpm 4.0.19__py3-none-any.whl → 4.0.20__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/VERSION +1 -1
- claude_mpm/__main__.py +4 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
- claude_mpm/agents/OUTPUT_STYLE.md +84 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/cli/__init__.py +23 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/commands/memory.py +32 -5
- claude_mpm/cli/commands/run.py +33 -6
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/utils.py +17 -4
- claude_mpm/core/base_service.py +1 -1
- claude_mpm/core/config.py +70 -5
- claude_mpm/core/framework_loader.py +342 -31
- claude_mpm/core/interactive_session.py +55 -1
- claude_mpm/core/oneshot_session.py +7 -1
- claude_mpm/core/output_style_manager.py +468 -0
- claude_mpm/core/unified_paths.py +190 -21
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
- claude_mpm/init.py +1 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +151 -7
- claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
- claude_mpm/services/agents/memory/__init__.py +0 -2
- claude_mpm/services/agents/memory/agent_memory_manager.py +737 -43
- claude_mpm/services/agents/memory/content_manager.py +144 -14
- claude_mpm/services/agents/memory/template_generator.py +7 -354
- claude_mpm/services/mcp_gateway/server/stdio_server.py +61 -169
- claude_mpm/services/subprocess_launcher_service.py +5 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.20.dist-info}/METADATA +1 -1
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.20.dist-info}/RECORD +37 -35
- claude_mpm/services/agents/memory/analyzer.py +0 -430
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.20.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.20.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.20.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.20.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
|
|
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
|
|
351
|
-
2. Agent memories from <agent
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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,260 @@ 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 summary
|
|
493
|
+
if loaded_count > 0 or skipped_count > 0:
|
|
494
|
+
self.logger.info(f"Memory loading complete: {loaded_count} memories loaded, {skipped_count} skipped")
|
|
495
|
+
|
|
496
|
+
def _load_memories_from_directory(
|
|
497
|
+
self,
|
|
498
|
+
memories_dir: Path,
|
|
499
|
+
deployed_agents: set,
|
|
500
|
+
pm_memories: list,
|
|
501
|
+
agent_memories_dict: dict,
|
|
502
|
+
source: str
|
|
503
|
+
) -> tuple[int, int]:
|
|
504
|
+
"""
|
|
505
|
+
Load memories from a specific directory.
|
|
506
|
+
|
|
507
|
+
Args:
|
|
508
|
+
memories_dir: Directory to load memories from
|
|
509
|
+
deployed_agents: Set of deployed agent names
|
|
510
|
+
pm_memories: List to append PM memories to
|
|
511
|
+
agent_memories_dict: Dict to store agent memories
|
|
512
|
+
source: Source label ("user" or "project")
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
Tuple of (loaded_count, skipped_count)
|
|
516
|
+
"""
|
|
517
|
+
loaded_count = 0
|
|
518
|
+
skipped_count = 0
|
|
519
|
+
|
|
372
520
|
# Load PM memories (always loaded)
|
|
373
|
-
|
|
521
|
+
# Support migration from both old formats
|
|
522
|
+
pm_memory_path = memories_dir / "PM_memories.md"
|
|
523
|
+
old_pm_path = memories_dir / "PM.md"
|
|
524
|
+
|
|
525
|
+
# Migrate from old PM.md if needed
|
|
526
|
+
if not pm_memory_path.exists() and old_pm_path.exists():
|
|
527
|
+
try:
|
|
528
|
+
old_pm_path.rename(pm_memory_path)
|
|
529
|
+
self.logger.info(f"Migrated PM.md to PM_memories.md")
|
|
530
|
+
except Exception as e:
|
|
531
|
+
self.logger.error(f"Failed to migrate PM.md: {e}")
|
|
532
|
+
pm_memory_path = old_pm_path # Fall back to old path
|
|
374
533
|
if pm_memory_path.exists():
|
|
375
534
|
loaded_content = self._try_load_file(
|
|
376
|
-
pm_memory_path, "PM memory"
|
|
535
|
+
pm_memory_path, f"PM memory ({source})"
|
|
377
536
|
)
|
|
378
537
|
if loaded_content:
|
|
379
|
-
|
|
538
|
+
pm_memories.append({
|
|
539
|
+
"source": source,
|
|
540
|
+
"content": loaded_content,
|
|
541
|
+
"path": pm_memory_path
|
|
542
|
+
})
|
|
380
543
|
memory_size = len(loaded_content.encode('utf-8'))
|
|
381
|
-
self.logger.info(f"Loaded PM memory: {pm_memory_path} ({memory_size:,} bytes)")
|
|
544
|
+
self.logger.info(f"Loaded {source} PM memory: {pm_memory_path} ({memory_size:,} bytes)")
|
|
382
545
|
loaded_count += 1
|
|
383
|
-
else:
|
|
384
|
-
self.logger.debug(f"No PM memory found at: {pm_memory_path}")
|
|
385
546
|
|
|
386
547
|
# Load agent memories (only for deployed agents)
|
|
387
548
|
for memory_file in memories_dir.glob("*.md"):
|
|
388
|
-
# Skip PM.md as we already handled
|
|
389
|
-
if memory_file.name
|
|
549
|
+
# Skip PM_memories.md and PM.md as we already handled them
|
|
550
|
+
if memory_file.name in ["PM_memories.md", "PM.md"]:
|
|
390
551
|
continue
|
|
391
552
|
|
|
392
553
|
# Extract agent name from file
|
|
554
|
+
# Support migration from old formats to new {agent_name}_memories.md format
|
|
393
555
|
agent_name = memory_file.stem
|
|
556
|
+
new_path = None
|
|
557
|
+
|
|
558
|
+
if agent_name.endswith("_agent"):
|
|
559
|
+
# Old format: {agent_name}_agent.md
|
|
560
|
+
agent_name = agent_name[:-6] # Remove "_agent" suffix
|
|
561
|
+
new_path = memories_dir / f"{agent_name}_memories.md"
|
|
562
|
+
elif agent_name.endswith("_memories"):
|
|
563
|
+
# Already in new format: {agent_name}_memories.md
|
|
564
|
+
agent_name = agent_name[:-9] # Remove "_memories" suffix
|
|
565
|
+
elif memory_file.name != "README.md":
|
|
566
|
+
# Intermediate format: {agent_name}.md
|
|
567
|
+
# agent_name already set from stem
|
|
568
|
+
new_path = memories_dir / f"{agent_name}_memories.md"
|
|
569
|
+
|
|
570
|
+
# Skip README.md
|
|
571
|
+
if memory_file.name == "README.md":
|
|
572
|
+
continue
|
|
573
|
+
|
|
574
|
+
# Migrate if needed
|
|
575
|
+
if new_path and not new_path.exists():
|
|
576
|
+
self._migrate_memory_file(memory_file, new_path)
|
|
394
577
|
|
|
395
578
|
# Check if agent is deployed
|
|
396
579
|
if agent_name in deployed_agents:
|
|
397
580
|
loaded_content = self._try_load_file(
|
|
398
|
-
memory_file, f"agent memory: {agent_name}"
|
|
581
|
+
memory_file, f"agent memory: {agent_name} ({source})"
|
|
399
582
|
)
|
|
400
583
|
if loaded_content:
|
|
401
|
-
# Store agent memories
|
|
402
|
-
if
|
|
403
|
-
|
|
404
|
-
|
|
584
|
+
# Store or merge agent memories
|
|
585
|
+
if agent_name not in agent_memories_dict:
|
|
586
|
+
agent_memories_dict[agent_name] = []
|
|
587
|
+
|
|
588
|
+
# If it's a list, append the new memory entry
|
|
589
|
+
if isinstance(agent_memories_dict[agent_name], list):
|
|
590
|
+
agent_memories_dict[agent_name].append({
|
|
591
|
+
"source": source,
|
|
592
|
+
"content": loaded_content,
|
|
593
|
+
"path": memory_file
|
|
594
|
+
})
|
|
595
|
+
|
|
405
596
|
memory_size = len(loaded_content.encode('utf-8'))
|
|
406
|
-
self.logger.info(f"Loaded
|
|
597
|
+
self.logger.info(f"Loaded {source} memory for {agent_name}: {memory_file.name} ({memory_size:,} bytes)")
|
|
407
598
|
loaded_count += 1
|
|
408
599
|
else:
|
|
409
|
-
self.logger.info(f"Skipped
|
|
600
|
+
self.logger.info(f"Skipped {source} memory: {memory_file.name} (agent not deployed)")
|
|
410
601
|
skipped_count += 1
|
|
411
602
|
|
|
412
|
-
#
|
|
413
|
-
|
|
414
|
-
|
|
603
|
+
# After loading all memories for this directory, aggregate agent memories
|
|
604
|
+
for agent_name in list(agent_memories_dict.keys()):
|
|
605
|
+
if isinstance(agent_memories_dict[agent_name], list) and agent_memories_dict[agent_name]:
|
|
606
|
+
# Aggregate memories for this agent
|
|
607
|
+
aggregated = self._aggregate_memories(agent_memories_dict[agent_name])
|
|
608
|
+
agent_memories_dict[agent_name] = aggregated
|
|
609
|
+
|
|
610
|
+
return loaded_count, skipped_count
|
|
611
|
+
|
|
612
|
+
def _aggregate_memories(self, memory_entries: list) -> str:
|
|
613
|
+
"""
|
|
614
|
+
Aggregate multiple memory entries into a single memory string.
|
|
615
|
+
|
|
616
|
+
Strategy:
|
|
617
|
+
- Parse memories by sections
|
|
618
|
+
- Merge sections, with project-level taking precedence
|
|
619
|
+
- Remove exact duplicates within sections
|
|
620
|
+
- Preserve unique entries from both sources
|
|
621
|
+
|
|
622
|
+
Args:
|
|
623
|
+
memory_entries: List of memory entries with source, content, and path
|
|
624
|
+
|
|
625
|
+
Returns:
|
|
626
|
+
Aggregated memory content as a string
|
|
627
|
+
"""
|
|
628
|
+
if not memory_entries:
|
|
629
|
+
return ""
|
|
630
|
+
|
|
631
|
+
# If only one entry, return it as-is
|
|
632
|
+
if len(memory_entries) == 1:
|
|
633
|
+
return memory_entries[0]["content"]
|
|
634
|
+
|
|
635
|
+
# Parse all memories into sections
|
|
636
|
+
all_sections = {}
|
|
637
|
+
metadata_lines = []
|
|
638
|
+
|
|
639
|
+
for entry in memory_entries:
|
|
640
|
+
content = entry["content"]
|
|
641
|
+
source = entry["source"]
|
|
642
|
+
|
|
643
|
+
# Parse content into sections
|
|
644
|
+
current_section = None
|
|
645
|
+
current_items = []
|
|
646
|
+
|
|
647
|
+
for line in content.split('\n'):
|
|
648
|
+
# Check for metadata lines
|
|
649
|
+
if line.startswith('<!-- ') and line.endswith(' -->'):
|
|
650
|
+
# Only keep metadata from project source or if not already present
|
|
651
|
+
if source == "project" or line not in metadata_lines:
|
|
652
|
+
metadata_lines.append(line)
|
|
653
|
+
# Check for section headers (## Level 2 headers)
|
|
654
|
+
elif line.startswith('## '):
|
|
655
|
+
# Save previous section if exists
|
|
656
|
+
if current_section and current_items:
|
|
657
|
+
if current_section not in all_sections:
|
|
658
|
+
all_sections[current_section] = {}
|
|
659
|
+
# Store items with their source
|
|
660
|
+
for item in current_items:
|
|
661
|
+
# Use content as key to detect duplicates
|
|
662
|
+
all_sections[current_section][item] = source
|
|
663
|
+
|
|
664
|
+
# Start new section
|
|
665
|
+
current_section = line
|
|
666
|
+
current_items = []
|
|
667
|
+
# Check for content lines (skip empty lines)
|
|
668
|
+
elif line.strip() and current_section:
|
|
669
|
+
current_items.append(line)
|
|
670
|
+
|
|
671
|
+
# Save last section
|
|
672
|
+
if current_section and current_items:
|
|
673
|
+
if current_section not in all_sections:
|
|
674
|
+
all_sections[current_section] = {}
|
|
675
|
+
for item in current_items:
|
|
676
|
+
# Project source overrides user source
|
|
677
|
+
if item not in all_sections[current_section] or source == "project":
|
|
678
|
+
all_sections[current_section][item] = source
|
|
679
|
+
|
|
680
|
+
# Build aggregated content
|
|
681
|
+
lines = []
|
|
682
|
+
|
|
683
|
+
# Add metadata
|
|
684
|
+
if metadata_lines:
|
|
685
|
+
lines.extend(metadata_lines)
|
|
686
|
+
lines.append("")
|
|
687
|
+
|
|
688
|
+
# Add header
|
|
689
|
+
lines.append("# Aggregated Memory")
|
|
690
|
+
lines.append("")
|
|
691
|
+
lines.append("*This memory combines user-level and project-level memories.*")
|
|
692
|
+
lines.append("")
|
|
693
|
+
|
|
694
|
+
# Add sections
|
|
695
|
+
for section_header in sorted(all_sections.keys()):
|
|
696
|
+
lines.append(section_header)
|
|
697
|
+
section_items = all_sections[section_header]
|
|
698
|
+
|
|
699
|
+
# Sort items to ensure consistent output
|
|
700
|
+
for item in sorted(section_items.keys()):
|
|
701
|
+
lines.append(item)
|
|
702
|
+
|
|
703
|
+
lines.append("") # Empty line after section
|
|
704
|
+
|
|
705
|
+
return '\n'.join(lines)
|
|
415
706
|
|
|
416
707
|
def _load_single_agent(
|
|
417
708
|
self, agent_file: Path
|
|
@@ -498,7 +789,7 @@ class FrameworkLoader:
|
|
|
498
789
|
"project_workflow": "",
|
|
499
790
|
"memory_instructions": "",
|
|
500
791
|
"project_memory": "",
|
|
501
|
-
"actual_memories": "", # Add field for actual memories from
|
|
792
|
+
"actual_memories": "", # Add field for actual memories from PM_memories.md
|
|
502
793
|
}
|
|
503
794
|
|
|
504
795
|
# Load instructions file from working directory
|
|
@@ -551,7 +842,7 @@ class FrameworkLoader:
|
|
|
551
842
|
# Load MEMORY.md - check for project-specific first, then system
|
|
552
843
|
self._load_memory_instructions(content)
|
|
553
844
|
|
|
554
|
-
# Load actual memories from .claude-mpm/memories/
|
|
845
|
+
# Load actual memories from .claude-mpm/memories/PM_memories.md
|
|
555
846
|
self._load_actual_memories(content)
|
|
556
847
|
|
|
557
848
|
# Discover agent directories
|
|
@@ -742,6 +1033,17 @@ class FrameworkLoader:
|
|
|
742
1033
|
"""Format full framework instructions."""
|
|
743
1034
|
from datetime import datetime
|
|
744
1035
|
|
|
1036
|
+
# Initialize output style manager on first use (ensures content is loaded)
|
|
1037
|
+
if self.output_style_manager is None:
|
|
1038
|
+
self._initialize_output_style()
|
|
1039
|
+
|
|
1040
|
+
# Check if we need to inject output style content for older Claude versions
|
|
1041
|
+
inject_output_style = False
|
|
1042
|
+
if self.output_style_manager:
|
|
1043
|
+
inject_output_style = self.output_style_manager.should_inject_content()
|
|
1044
|
+
if inject_output_style:
|
|
1045
|
+
self.logger.info("Injecting output style content into instructions for Claude < 1.0.83")
|
|
1046
|
+
|
|
745
1047
|
# If we have the full framework INSTRUCTIONS.md, use it
|
|
746
1048
|
if self.framework_content.get("framework_instructions"):
|
|
747
1049
|
instructions = self._strip_metadata_comments(
|
|
@@ -789,6 +1091,15 @@ class FrameworkLoader:
|
|
|
789
1091
|
self.framework_content["base_pm_instructions"]
|
|
790
1092
|
)
|
|
791
1093
|
instructions += f"\n\n{base_pm}"
|
|
1094
|
+
|
|
1095
|
+
# Inject output style content if needed (for Claude < 1.0.83)
|
|
1096
|
+
if inject_output_style and self.output_style_manager:
|
|
1097
|
+
output_style_content = self.output_style_manager.get_injectable_content(framework_loader=self)
|
|
1098
|
+
if output_style_content:
|
|
1099
|
+
instructions += "\n\n## Output Style Configuration\n"
|
|
1100
|
+
instructions += "**Note: The following output style is injected for Claude < 1.0.83**\n\n"
|
|
1101
|
+
instructions += output_style_content
|
|
1102
|
+
instructions += "\n"
|
|
792
1103
|
|
|
793
1104
|
# Clean up any trailing whitespace
|
|
794
1105
|
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,18 +297,50 @@ 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."""
|
|
@@ -315,8 +348,15 @@ class InteractiveSession:
|
|
|
315
348
|
|
|
316
349
|
# Add custom arguments
|
|
317
350
|
if self.runner.claude_args:
|
|
351
|
+
# Enhanced debug logging for --resume flag verification
|
|
352
|
+
self.logger.debug(f"Raw claude_args received: {self.runner.claude_args}")
|
|
353
|
+
|
|
354
|
+
# Check explicitly for --resume flag
|
|
355
|
+
has_resume = "--resume" in self.runner.claude_args
|
|
356
|
+
self.logger.info(f"--resume flag present in claude_args: {has_resume}")
|
|
357
|
+
|
|
318
358
|
cmd.extend(self.runner.claude_args)
|
|
319
|
-
|
|
359
|
+
|
|
320
360
|
# Add system instructions
|
|
321
361
|
from claude_mpm.core.claude_runner import create_simple_context
|
|
322
362
|
|
|
@@ -324,6 +364,16 @@ class InteractiveSession:
|
|
|
324
364
|
if system_prompt and system_prompt != create_simple_context():
|
|
325
365
|
cmd.extend(["--append-system-prompt", system_prompt])
|
|
326
366
|
|
|
367
|
+
# Final command verification
|
|
368
|
+
# self.logger.info(f"Final Claude command built: {' '.join(cmd)}")
|
|
369
|
+
|
|
370
|
+
# Explicit --resume flag verification
|
|
371
|
+
if "--resume" in cmd:
|
|
372
|
+
self.logger.info("✅ VERIFIED: --resume flag IS included in final command")
|
|
373
|
+
self.logger.debug(f"--resume position in command: {cmd.index('--resume')}")
|
|
374
|
+
else:
|
|
375
|
+
self.logger.debug("ℹ️ --resume flag NOT included in final command")
|
|
376
|
+
|
|
327
377
|
return cmd
|
|
328
378
|
|
|
329
379
|
def _prepare_environment(self) -> dict:
|
|
@@ -341,6 +391,10 @@ class InteractiveSession:
|
|
|
341
391
|
for var in claude_vars_to_remove:
|
|
342
392
|
clean_env.pop(var, None)
|
|
343
393
|
|
|
394
|
+
# Disable telemetry for Claude Code
|
|
395
|
+
# This ensures Claude Code doesn't send telemetry data during runtime
|
|
396
|
+
clean_env["DISABLE_TELEMETRY"] = "1"
|
|
397
|
+
|
|
344
398
|
return clean_env
|
|
345
399
|
|
|
346
400
|
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
|
-
|
|
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."""
|