claude-mpm 4.4.0__py3-none-any.whl → 4.4.4__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 (129) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/WORKFLOW.md +2 -14
  3. claude_mpm/agents/agent_loader.py +3 -2
  4. claude_mpm/agents/agent_loader_integration.py +2 -1
  5. claude_mpm/agents/async_agent_loader.py +2 -2
  6. claude_mpm/agents/base_agent_loader.py +2 -2
  7. claude_mpm/agents/frontmatter_validator.py +1 -0
  8. claude_mpm/agents/system_agent_config.py +2 -1
  9. claude_mpm/cli/commands/configure.py +2 -29
  10. claude_mpm/cli/commands/doctor.py +44 -5
  11. claude_mpm/cli/commands/mpm_init.py +117 -63
  12. claude_mpm/cli/parsers/configure_parser.py +6 -15
  13. claude_mpm/cli/startup_logging.py +1 -3
  14. claude_mpm/config/agent_config.py +1 -1
  15. claude_mpm/config/paths.py +2 -1
  16. claude_mpm/core/agent_name_normalizer.py +1 -0
  17. claude_mpm/core/config.py +2 -1
  18. claude_mpm/core/config_aliases.py +2 -1
  19. claude_mpm/core/file_utils.py +0 -1
  20. claude_mpm/core/framework/__init__.py +38 -0
  21. claude_mpm/core/framework/formatters/__init__.py +11 -0
  22. claude_mpm/core/framework/formatters/capability_generator.py +367 -0
  23. claude_mpm/core/framework/formatters/content_formatter.py +288 -0
  24. claude_mpm/core/framework/formatters/context_generator.py +184 -0
  25. claude_mpm/core/framework/loaders/__init__.py +13 -0
  26. claude_mpm/core/framework/loaders/agent_loader.py +206 -0
  27. claude_mpm/core/framework/loaders/file_loader.py +223 -0
  28. claude_mpm/core/framework/loaders/instruction_loader.py +161 -0
  29. claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
  30. claude_mpm/core/framework/processors/__init__.py +11 -0
  31. claude_mpm/core/framework/processors/memory_processor.py +230 -0
  32. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  33. claude_mpm/core/framework/processors/template_processor.py +244 -0
  34. claude_mpm/core/framework_loader.py +298 -1795
  35. claude_mpm/core/log_manager.py +2 -1
  36. claude_mpm/core/tool_access_control.py +1 -0
  37. claude_mpm/core/unified_agent_registry.py +2 -1
  38. claude_mpm/core/unified_paths.py +1 -0
  39. claude_mpm/experimental/cli_enhancements.py +1 -0
  40. claude_mpm/hooks/__init__.py +9 -1
  41. claude_mpm/hooks/base_hook.py +1 -0
  42. claude_mpm/hooks/instruction_reinforcement.py +1 -0
  43. claude_mpm/hooks/kuzu_memory_hook.py +359 -0
  44. claude_mpm/hooks/validation_hooks.py +1 -1
  45. claude_mpm/scripts/mpm_doctor.py +1 -0
  46. claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
  47. claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
  48. claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
  49. claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
  50. claude_mpm/services/agents/management/agent_management_service.py +1 -1
  51. claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
  52. claude_mpm/services/agents/memory/memory_file_service.py +6 -2
  53. claude_mpm/services/agents/memory/memory_format_service.py +0 -1
  54. claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
  55. claude_mpm/services/async_session_logger.py +1 -1
  56. claude_mpm/services/claude_session_logger.py +1 -0
  57. claude_mpm/services/core/path_resolver.py +2 -0
  58. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  59. claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
  60. claude_mpm/services/diagnostics/checks/mcp_services_check.py +399 -0
  61. claude_mpm/services/diagnostics/diagnostic_runner.py +4 -0
  62. claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
  63. claude_mpm/services/event_bus/direct_relay.py +2 -1
  64. claude_mpm/services/event_bus/event_bus.py +1 -0
  65. claude_mpm/services/event_bus/relay.py +3 -2
  66. claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
  67. claude_mpm/services/infrastructure/daemon_manager.py +1 -1
  68. claude_mpm/services/mcp_config_manager.py +67 -4
  69. claude_mpm/services/mcp_gateway/core/process_pool.py +320 -0
  70. claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
  71. claude_mpm/services/mcp_gateway/main.py +3 -13
  72. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
  73. claude_mpm/services/mcp_gateway/tools/__init__.py +14 -2
  74. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +38 -6
  75. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +527 -0
  76. claude_mpm/services/memory/cache/simple_cache.py +1 -1
  77. claude_mpm/services/project/archive_manager.py +159 -96
  78. claude_mpm/services/project/documentation_manager.py +64 -45
  79. claude_mpm/services/project/enhanced_analyzer.py +132 -89
  80. claude_mpm/services/project/project_organizer.py +225 -131
  81. claude_mpm/services/response_tracker.py +1 -1
  82. claude_mpm/services/shared/__init__.py +2 -1
  83. claude_mpm/services/shared/service_factory.py +8 -5
  84. claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
  85. claude_mpm/services/unified/__init__.py +1 -1
  86. claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
  87. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
  88. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
  89. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
  90. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
  91. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
  92. claude_mpm/services/unified/config_strategies/__init__.py +175 -0
  93. claude_mpm/services/unified/config_strategies/config_schema.py +735 -0
  94. claude_mpm/services/unified/config_strategies/context_strategy.py +750 -0
  95. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1009 -0
  96. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +879 -0
  97. claude_mpm/services/unified/config_strategies/unified_config_service.py +814 -0
  98. claude_mpm/services/unified/config_strategies/validation_strategy.py +1144 -0
  99. claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
  100. claude_mpm/services/unified/deployment_strategies/base.py +24 -28
  101. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
  102. claude_mpm/services/unified/deployment_strategies/local.py +49 -34
  103. claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
  104. claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
  105. claude_mpm/services/unified/interfaces.py +0 -26
  106. claude_mpm/services/unified/migration.py +17 -40
  107. claude_mpm/services/unified/strategies.py +9 -26
  108. claude_mpm/services/unified/unified_analyzer.py +48 -44
  109. claude_mpm/services/unified/unified_config.py +21 -19
  110. claude_mpm/services/unified/unified_deployment.py +21 -26
  111. claude_mpm/storage/state_storage.py +1 -0
  112. claude_mpm/utils/agent_dependency_loader.py +18 -6
  113. claude_mpm/utils/common.py +14 -12
  114. claude_mpm/utils/database_connector.py +15 -12
  115. claude_mpm/utils/error_handler.py +1 -0
  116. claude_mpm/utils/log_cleanup.py +1 -0
  117. claude_mpm/utils/path_operations.py +1 -0
  118. claude_mpm/utils/session_logging.py +1 -1
  119. claude_mpm/utils/subprocess_utils.py +1 -0
  120. claude_mpm/validation/agent_validator.py +1 -1
  121. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +23 -17
  122. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +126 -105
  123. claude_mpm/cli/commands/configure_tui.py +0 -1927
  124. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
  125. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
  126. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
  127. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
  128. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
  129. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/top_level.txt +0 -0
@@ -25,12 +25,13 @@ import shutil
25
25
  import subprocess
26
26
  from datetime import datetime, timedelta
27
27
  from pathlib import Path
28
- from typing import Any, Dict, List, Optional, Set, Tuple
28
+ from typing import Dict, List, Optional, Tuple
29
29
 
30
30
  from rich.console import Console
31
31
  from rich.table import Table
32
32
 
33
33
  from claude_mpm.core.logging_utils import get_logger
34
+
34
35
  logger = get_logger(__name__)
35
36
  console = Console()
36
37
 
@@ -125,13 +126,15 @@ Generated by Claude MPM Archive Manager
125
126
  # Create metadata file if provided
126
127
  if metadata or reason:
127
128
  meta_data = metadata or {}
128
- meta_data.update({
129
- "original_path": str(file_path),
130
- "archived_at": datetime.now().isoformat(),
131
- "reason": reason or "Manual archive",
132
- "file_size": file_path.stat().st_size,
133
- "file_hash": self._calculate_file_hash(file_path),
134
- })
129
+ meta_data.update(
130
+ {
131
+ "original_path": str(file_path),
132
+ "archived_at": datetime.now().isoformat(),
133
+ "reason": reason or "Manual archive",
134
+ "file_size": file_path.stat().st_size,
135
+ "file_hash": self._calculate_file_hash(file_path),
136
+ }
137
+ )
135
138
 
136
139
  meta_path = self.archive_path / f"{archive_name}.meta.json"
137
140
  meta_path.write_text(json.dumps(meta_data, indent=2))
@@ -252,7 +255,9 @@ Generated by Claude MPM Archive Manager
252
255
  "name": archive_file.name,
253
256
  "path": str(archive_file),
254
257
  "size": archive_file.stat().st_size,
255
- "modified": datetime.fromtimestamp(archive_file.stat().st_mtime).isoformat(),
258
+ "modified": datetime.fromtimestamp(
259
+ archive_file.stat().st_mtime
260
+ ).isoformat(),
256
261
  "compressed": archive_file.suffix == ".gz",
257
262
  }
258
263
 
@@ -308,7 +313,9 @@ Generated by Claude MPM Archive Manager
308
313
  # Backup current file if it exists
309
314
  if target_path.exists():
310
315
  self.archive_file(
311
- target_path, reason="Backup before restoration", metadata={"restoration_from": archive_name}
316
+ target_path,
317
+ reason="Backup before restoration",
318
+ metadata={"restoration_from": archive_name},
312
319
  )
313
320
 
314
321
  # Restore file
@@ -325,7 +332,7 @@ Generated by Claude MPM Archive Manager
325
332
 
326
333
  except Exception as e:
327
334
  logger.error(f"Failed to restore {archive_name}: {e}")
328
- return False, f"Restoration failed: {str(e)}"
335
+ return False, f"Restoration failed: {e!s}"
329
336
 
330
337
  def compare_with_archive(self, current_file: Path, archive_name: str) -> Dict:
331
338
  """Compare current file with an archived version."""
@@ -366,7 +373,7 @@ Generated by Claude MPM Archive Manager
366
373
  }
367
374
 
368
375
  except Exception as e:
369
- return {"error": f"Comparison failed: {str(e)}"}
376
+ return {"error": f"Comparison failed: {e!s}"}
370
377
 
371
378
  def create_archive_report(self) -> Dict:
372
379
  """Generate a report of all archives."""
@@ -462,13 +469,15 @@ Generated by Claude MPM Archive Manager
462
469
  return []
463
470
 
464
471
  relative_path = file_path.relative_to(self.project_path)
465
- output = self._run_git_command([
466
- "log",
467
- f"-{limit}",
468
- "--pretty=format:%H|%an|%at|%s",
469
- "--follow",
470
- str(relative_path),
471
- ])
472
+ output = self._run_git_command(
473
+ [
474
+ "log",
475
+ f"-{limit}",
476
+ "--pretty=format:%H|%an|%at|%s",
477
+ "--follow",
478
+ str(relative_path),
479
+ ]
480
+ )
472
481
 
473
482
  if not output:
474
483
  return []
@@ -477,12 +486,14 @@ Generated by Claude MPM Archive Manager
477
486
  for line in output.splitlines():
478
487
  parts = line.split("|", 3)
479
488
  if len(parts) == 4:
480
- commits.append({
481
- "hash": parts[0][:8],
482
- "author": parts[1],
483
- "date": datetime.fromtimestamp(int(parts[2])).isoformat(),
484
- "message": parts[3],
485
- })
489
+ commits.append(
490
+ {
491
+ "hash": parts[0][:8],
492
+ "author": parts[1],
493
+ "date": datetime.fromtimestamp(int(parts[2])).isoformat(),
494
+ "message": parts[3],
495
+ }
496
+ )
486
497
  return commits
487
498
 
488
499
  def get_file_last_modified(self, file_path: Path) -> Optional[datetime]:
@@ -491,12 +502,14 @@ Generated by Claude MPM Archive Manager
491
502
  return None
492
503
 
493
504
  relative_path = file_path.relative_to(self.project_path)
494
- output = self._run_git_command([
495
- "log",
496
- "-1",
497
- "--format=%at",
498
- str(relative_path),
499
- ])
505
+ output = self._run_git_command(
506
+ [
507
+ "log",
508
+ "-1",
509
+ "--format=%at",
510
+ str(relative_path),
511
+ ]
512
+ )
500
513
 
501
514
  if output:
502
515
  return datetime.fromtimestamp(int(output))
@@ -523,10 +536,12 @@ Generated by Claude MPM Archive Manager
523
536
 
524
537
  # Check for outdated content
525
538
  if file_report.get("outdated_indicators"):
526
- report["outdated_sections"].append({
527
- "file": doc_file,
528
- "indicators": file_report["outdated_indicators"],
529
- })
539
+ report["outdated_sections"].append(
540
+ {
541
+ "file": doc_file,
542
+ "indicators": file_report["outdated_indicators"],
543
+ }
544
+ )
530
545
 
531
546
  # Check synchronization between docs
532
547
  sync_issues = self._check_documentation_sync()
@@ -561,10 +576,12 @@ Generated by Claude MPM Archive Manager
561
576
  for pattern_name, pattern in self.version_patterns.items():
562
577
  matches = pattern.findall(content)
563
578
  if matches:
564
- report["version_references"].append({
565
- "type": pattern_name,
566
- "versions": matches[:5], # First 5 matches
567
- })
579
+ report["version_references"].append(
580
+ {
581
+ "type": pattern_name,
582
+ "versions": matches[:5], # First 5 matches
583
+ }
584
+ )
568
585
 
569
586
  # Detect outdated indicators
570
587
  outdated_indicators = self._detect_outdated_content(content, file_path.name)
@@ -591,11 +608,13 @@ Generated by Claude MPM Archive Manager
591
608
  regex = re.compile(pattern, re.IGNORECASE)
592
609
  for i, line in enumerate(lines, 1):
593
610
  if regex.search(line):
594
- indicators.append({
595
- "line": i,
596
- "type": description,
597
- "content": line.strip()[:100], # First 100 chars
598
- })
611
+ indicators.append(
612
+ {
613
+ "line": i,
614
+ "type": description,
615
+ "content": line.strip()[:100], # First 100 chars
616
+ }
617
+ )
599
618
 
600
619
  # Check for old version numbers if VERSION file exists
601
620
  version_file = self.project_path / "VERSION"
@@ -605,13 +624,17 @@ Generated by Claude MPM Archive Manager
605
624
 
606
625
  for match in old_version_pattern.finditer(content):
607
626
  found_version = match.group(0)
608
- if found_version != current_version and self._is_older_version(found_version, current_version):
609
- pos = content[:match.start()].count('\n') + 1
610
- indicators.append({
611
- "line": pos,
612
- "type": "Old version reference",
613
- "content": f"Found {found_version} (current: {current_version})",
614
- })
627
+ if found_version != current_version and self._is_older_version(
628
+ found_version, current_version
629
+ ):
630
+ pos = content[: match.start()].count("\n") + 1
631
+ indicators.append(
632
+ {
633
+ "line": pos,
634
+ "type": "Old version reference",
635
+ "content": f"Found {found_version} (current: {current_version})",
636
+ }
637
+ )
615
638
 
616
639
  return indicators[:20] # Limit to 20 most relevant
617
640
 
@@ -648,22 +671,28 @@ Generated by Claude MPM Archive Manager
648
671
 
649
672
  if claude_versions and readme_versions:
650
673
  if claude_versions[0] != readme_versions[0]:
651
- issues.append({
652
- "type": "Version mismatch",
653
- "files": ["CLAUDE.md", "README.md"],
654
- "details": f"CLAUDE.md: {claude_versions[0]}, README.md: {readme_versions[0]}",
655
- })
674
+ issues.append(
675
+ {
676
+ "type": "Version mismatch",
677
+ "files": ["CLAUDE.md", "README.md"],
678
+ "details": f"CLAUDE.md: {claude_versions[0]}, README.md: {readme_versions[0]}",
679
+ }
680
+ )
656
681
 
657
682
  # Check for project name consistency
658
- project_names = re.findall(r"Claude MPM|claude-mpm", readme_content, re.IGNORECASE)
683
+ project_names = re.findall(
684
+ r"Claude MPM|claude-mpm", readme_content, re.IGNORECASE
685
+ )
659
686
  if project_names:
660
687
  unique_names = set(project_names)
661
688
  if len(unique_names) > 1:
662
- issues.append({
663
- "type": "Inconsistent project naming",
664
- "files": ["README.md"],
665
- "details": f"Found variations: {', '.join(unique_names)}",
666
- })
689
+ issues.append(
690
+ {
691
+ "type": "Inconsistent project naming",
692
+ "files": ["README.md"],
693
+ "details": f"Found variations: {', '.join(unique_names)}",
694
+ }
695
+ )
667
696
 
668
697
  # Check CHANGELOG.md exists and is recent
669
698
  changelog_path = self.project_path / "CHANGELOG.md"
@@ -672,17 +701,21 @@ Generated by Claude MPM Archive Manager
672
701
  if last_modified:
673
702
  days_old = (datetime.now() - last_modified).days
674
703
  if days_old > 30:
675
- issues.append({
676
- "type": "Stale changelog",
677
- "files": ["CHANGELOG.md"],
678
- "details": f"Last updated {days_old} days ago",
679
- })
704
+ issues.append(
705
+ {
706
+ "type": "Stale changelog",
707
+ "files": ["CHANGELOG.md"],
708
+ "details": f"Last updated {days_old} days ago",
709
+ }
710
+ )
680
711
  else:
681
- issues.append({
682
- "type": "Missing file",
683
- "files": ["CHANGELOG.md"],
684
- "details": "CHANGELOG.md does not exist",
685
- })
712
+ issues.append(
713
+ {
714
+ "type": "Missing file",
715
+ "files": ["CHANGELOG.md"],
716
+ "details": "CHANGELOG.md does not exist",
717
+ }
718
+ )
686
719
 
687
720
  return issues
688
721
 
@@ -700,7 +733,9 @@ Generated by Claude MPM Archive Manager
700
733
  if report["synchronization_issues"]:
701
734
  for issue in report["synchronization_issues"]:
702
735
  if issue["type"] == "Version mismatch":
703
- recommendations.append("🔄 Synchronize version numbers across documentation files")
736
+ recommendations.append(
737
+ "🔄 Synchronize version numbers across documentation files"
738
+ )
704
739
  elif issue["type"] == "Stale changelog":
705
740
  recommendations.append("📅 Update CHANGELOG.md with recent changes")
706
741
  elif issue["type"] == "Missing file":
@@ -708,17 +743,29 @@ Generated by Claude MPM Archive Manager
708
743
 
709
744
  # Check for TODO items
710
745
  total_todos = sum(
711
- len([i for i in file_report.get("outdated_indicators", [])
712
- if i["type"] == "Unresolved TODOs"])
746
+ len(
747
+ [
748
+ i
749
+ for i in file_report.get("outdated_indicators", [])
750
+ if i["type"] == "Unresolved TODOs"
751
+ ]
752
+ )
713
753
  for file_report in report["files_reviewed"].values()
714
754
  )
715
755
  if total_todos > 0:
716
- recommendations.append(f"✅ Resolve {total_todos} TODO items in documentation")
756
+ recommendations.append(
757
+ f"✅ Resolve {total_todos} TODO items in documentation"
758
+ )
717
759
 
718
760
  # Check for deprecated references
719
761
  deprecated_count = sum(
720
- len([i for i in file_report.get("outdated_indicators", [])
721
- if "deprecated" in i["type"].lower()])
762
+ len(
763
+ [
764
+ i
765
+ for i in file_report.get("outdated_indicators", [])
766
+ if "deprecated" in i["type"].lower()
767
+ ]
768
+ )
722
769
  for file_report in report["files_reviewed"].values()
723
770
  )
724
771
  if deprecated_count > 0:
@@ -768,20 +815,24 @@ Generated by Claude MPM Archive Manager
768
815
  "auto_detection": True,
769
816
  "indicators": file_report.get("outdated_indicators", [])[:5],
770
817
  "review_timestamp": review["timestamp"],
771
- }
818
+ },
772
819
  )
773
820
  if archive_result:
774
- result["archived_files"].append({
821
+ result["archived_files"].append(
822
+ {
823
+ "file": filename,
824
+ "reason": archive_reason,
825
+ "archive_path": str(archive_result),
826
+ }
827
+ )
828
+ elif should_archive:
829
+ result["skipped_files"].append(
830
+ {
775
831
  "file": filename,
776
832
  "reason": archive_reason,
777
- "archive_path": str(archive_result),
778
- })
779
- elif should_archive:
780
- result["skipped_files"].append({
781
- "file": filename,
782
- "reason": archive_reason,
783
- "action": "Would archive (dry run)",
784
- })
833
+ "action": "Would archive (dry run)",
834
+ }
835
+ )
785
836
 
786
837
  return result
787
838
 
@@ -854,7 +905,9 @@ Generated by Claude MPM Archive Manager
854
905
  # Archive before update
855
906
  self.archive_file(readme_path, reason="Before version sync")
856
907
  readme_path.write_text(updated_readme)
857
- result["changes"].append(f"Updated README.md to version {current_version}")
908
+ result["changes"].append(
909
+ f"Updated README.md to version {current_version}"
910
+ )
858
911
 
859
912
  # Update CHANGELOG.md header if exists
860
913
  if changelog_path.exists() and current_version:
@@ -877,14 +930,18 @@ Generated by Claude MPM Archive Manager
877
930
  updated_changelog = "\n".join(lines)
878
931
 
879
932
  # Archive before update
880
- self.archive_file(changelog_path, reason="Before adding new version")
933
+ self.archive_file(
934
+ changelog_path, reason="Before adding new version"
935
+ )
881
936
  changelog_path.write_text(updated_changelog)
882
- result["changes"].append(f"Added {current_version} section to CHANGELOG.md")
937
+ result["changes"].append(
938
+ f"Added {current_version} section to CHANGELOG.md"
939
+ )
883
940
 
884
941
  result["synced"] = len(result["changes"]) > 0
885
942
 
886
943
  except Exception as e:
887
- result["errors"].append(f"Sync failed: {str(e)}")
944
+ result["errors"].append(f"Sync failed: {e!s}")
888
945
  logger.error(f"Documentation sync failed: {e}")
889
946
 
890
947
  return result
@@ -919,7 +976,9 @@ Generated by Claude MPM Archive Manager
919
976
  table.add_column("Last Updated", style="magenta")
920
977
 
921
978
  for filename, report in review["files_reviewed"].items():
922
- status = "✅ OK" if not report.get("outdated_indicators") else "⚠️ Needs Review"
979
+ status = (
980
+ "✅ OK" if not report.get("outdated_indicators") else "⚠️ Needs Review"
981
+ )
923
982
  issues = len(report.get("outdated_indicators", []))
924
983
 
925
984
  last_updated = "Unknown"
@@ -964,13 +1023,17 @@ Generated by Claude MPM Archive Manager
964
1023
 
965
1024
  # Generate diff if current file exists and review requested
966
1025
  if target_path.exists() and review_changes:
967
- diff_report = self.generate_documentation_diff_report(target_path, archive_file)
1026
+ diff_report = self.generate_documentation_diff_report(
1027
+ target_path, archive_file
1028
+ )
968
1029
 
969
1030
  console.print("\n[bold cyan]📝 Changes to be applied:[/bold cyan]")
970
1031
  console.print(diff_report)
971
1032
 
972
1033
  # Ask for confirmation
973
- console.print("\n[bold yellow]Proceed with restoration? (y/n): [/bold yellow]", end="")
1034
+ console.print(
1035
+ "\n[bold yellow]Proceed with restoration? (y/n): [/bold yellow]", end=""
1036
+ )
974
1037
  # In automated context, assume yes
975
1038
  confirm = True
976
1039
 
@@ -978,4 +1041,4 @@ Generated by Claude MPM Archive Manager
978
1041
  return False, "Restoration cancelled by user"
979
1042
 
980
1043
  # Proceed with restoration
981
- return self.restore_archive(archive_name, target_path)
1044
+ return self.restore_archive(archive_name, target_path)
@@ -18,15 +18,15 @@ Created: 2025-01-26
18
18
 
19
19
  import difflib
20
20
  import hashlib
21
- import json
22
21
  import re
23
22
  from datetime import datetime
24
23
  from pathlib import Path
25
- from typing import Dict, List, Optional, Set, Tuple
24
+ from typing import Dict, List, Tuple
26
25
 
27
26
  from rich.console import Console
28
27
 
29
28
  from claude_mpm.core.logging_utils import get_logger
29
+
30
30
  logger = get_logger(__name__)
31
31
  console = Console()
32
32
 
@@ -70,7 +70,9 @@ class DocumentationManager:
70
70
  if self.claude_md_path.exists():
71
71
  self.existing_content = self.claude_md_path.read_text(encoding="utf-8")
72
72
  self.content_hash = hashlib.md5(self.existing_content.encode()).hexdigest()
73
- logger.info(f"Loaded existing CLAUDE.md ({len(self.existing_content)} chars)")
73
+ logger.info(
74
+ f"Loaded existing CLAUDE.md ({len(self.existing_content)} chars)"
75
+ )
74
76
 
75
77
  def has_existing_documentation(self) -> bool:
76
78
  """Check if project has existing CLAUDE.md."""
@@ -118,15 +120,17 @@ class DocumentationManager:
118
120
  if line.startswith("#"):
119
121
  # Save previous section if exists
120
122
  if current_section:
121
- sections.append({
122
- "title": current_section,
123
- "level": current_level,
124
- "start_line": section_start,
125
- "end_line": i - 1,
126
- "content_preview": self._get_content_preview(
127
- lines[section_start:i]
128
- ),
129
- })
123
+ sections.append(
124
+ {
125
+ "title": current_section,
126
+ "level": current_level,
127
+ "start_line": section_start,
128
+ "end_line": i - 1,
129
+ "content_preview": self._get_content_preview(
130
+ lines[section_start:i]
131
+ ),
132
+ }
133
+ )
130
134
 
131
135
  # Parse new section
132
136
  level = len(line.split()[0])
@@ -137,13 +141,15 @@ class DocumentationManager:
137
141
 
138
142
  # Add last section
139
143
  if current_section:
140
- sections.append({
141
- "title": current_section,
142
- "level": current_level,
143
- "start_line": section_start,
144
- "end_line": len(lines) - 1,
145
- "content_preview": self._get_content_preview(lines[section_start:]),
146
- })
144
+ sections.append(
145
+ {
146
+ "title": current_section,
147
+ "level": current_level,
148
+ "start_line": section_start,
149
+ "end_line": len(lines) - 1,
150
+ "content_preview": self._get_content_preview(lines[section_start:]),
151
+ }
152
+ )
147
153
 
148
154
  return sections
149
155
 
@@ -160,13 +166,18 @@ class DocumentationManager:
160
166
 
161
167
  if self.existing_content:
162
168
  # Check for old patterns
163
- if "## Installation" in self.existing_content and "pip install" not in self.existing_content:
169
+ if (
170
+ "## Installation" in self.existing_content
171
+ and "pip install" not in self.existing_content
172
+ ):
164
173
  patterns.append("Missing installation instructions")
165
174
 
166
175
  if "TODO" in self.existing_content or "FIXME" in self.existing_content:
167
176
  patterns.append("Contains TODO/FIXME items")
168
177
 
169
- if not re.search(r"Last Updated:|Last Modified:", self.existing_content, re.IGNORECASE):
178
+ if not re.search(
179
+ r"Last Updated:|Last Modified:", self.existing_content, re.IGNORECASE
180
+ ):
170
181
  patterns.append("Missing update timestamp")
171
182
 
172
183
  if "```" not in self.existing_content:
@@ -193,12 +204,16 @@ class DocumentationManager:
193
204
  custom = []
194
205
  for section in sections:
195
206
  title_lower = section["title"].lower()
196
- if not any(re.search(pattern, title_lower) for pattern in standard_patterns):
207
+ if not any(
208
+ re.search(pattern, title_lower) for pattern in standard_patterns
209
+ ):
197
210
  custom.append(section["title"])
198
211
 
199
212
  return custom
200
213
 
201
- def merge_with_template(self, new_content: str, preserve_custom: bool = True) -> str:
214
+ def merge_with_template(
215
+ self, new_content: str, preserve_custom: bool = True
216
+ ) -> str:
202
217
  """Merge existing content with new template content."""
203
218
  if not self.existing_content:
204
219
  return new_content
@@ -279,35 +294,37 @@ class DocumentationManager:
279
294
  # Map to known section types
280
295
  if "priority" in title and "index" in title:
281
296
  return "priority_index"
282
- elif "critical" in title and "security" in title:
297
+ if "critical" in title and "security" in title:
283
298
  return "critical_security"
284
- elif "critical" in title and "business" in title:
299
+ if "critical" in title and "business" in title:
285
300
  return "critical_business"
286
- elif "important" in title and "architecture" in title:
301
+ if "important" in title and "architecture" in title:
287
302
  return "important_architecture"
288
- elif "important" in title and "workflow" in title:
303
+ if "important" in title and "workflow" in title:
289
304
  return "important_workflow"
290
- elif "project" in title and "overview" in title:
305
+ if "project" in title and "overview" in title:
291
306
  return "project_overview"
292
- elif "standard" in title and "coding" in title:
307
+ if "standard" in title and "coding" in title:
293
308
  return "standard_coding"
294
- elif "standard" in title and "tasks" in title:
309
+ if "standard" in title and "tasks" in title:
295
310
  return "standard_tasks"
296
- elif "documentation" in title:
311
+ if "documentation" in title:
297
312
  return "documentation_links"
298
- elif "optional" in title or "future" in title:
313
+ if "optional" in title or "future" in title:
299
314
  return "optional_future"
300
- elif "meta" in title or "maintain" in title:
315
+ if "meta" in title or "maintain" in title:
301
316
  return "meta_maintenance"
302
- else:
303
- return "unknown"
317
+ return "unknown"
304
318
 
305
319
  def _merge_section_content(
306
320
  self, existing: str, new: str, section_header: str
307
321
  ) -> str:
308
322
  """Merge content from existing and new sections."""
309
323
  # For critical sections, prefer new content but append unique existing items
310
- if "critical" in section_header.lower() or "important" in section_header.lower():
324
+ if (
325
+ "critical" in section_header.lower()
326
+ or "important" in section_header.lower()
327
+ ):
311
328
  # Extract bullet points from both
312
329
  existing_items = self._extract_bullet_points(existing)
313
330
  new_items = self._extract_bullet_points(new)
@@ -321,13 +338,11 @@ class DocumentationManager:
321
338
  # Reconstruct section
322
339
  if all_items:
323
340
  return "\n".join([""] + all_items + [""])
324
- else:
325
- return new
326
- else:
327
- # For other sections, use new as base and append existing
328
- if existing.strip() and existing.strip() != new.strip():
329
- return f"{new}\n\n<!-- Preserved from previous version -->\n{existing}"
330
341
  return new
342
+ # For other sections, use new as base and append existing
343
+ if existing.strip() and existing.strip() != new.strip():
344
+ return f"{new}\n\n<!-- Preserved from previous version -->\n{existing}"
345
+ return new
331
346
 
332
347
  def _extract_bullet_points(self, content: str) -> List[str]:
333
348
  """Extract bullet points from content."""
@@ -343,7 +358,9 @@ class DocumentationManager:
343
358
  for existing in items:
344
359
  existing_clean = re.sub(r"[^a-zA-Z0-9\s]", "", existing.lower())
345
360
  # Use fuzzy matching for similarity
346
- similarity = difflib.SequenceMatcher(None, item_clean, existing_clean).ratio()
361
+ similarity = difflib.SequenceMatcher(
362
+ None, item_clean, existing_clean
363
+ ).ratio()
347
364
  if similarity > 0.8: # 80% similarity threshold
348
365
  return True
349
366
  return False
@@ -464,7 +481,9 @@ class DocumentationManager:
464
481
  issues.append(f"Missing required section: {section}")
465
482
 
466
483
  # Check for priority markers
467
- has_markers = any(marker in content for marker in self.PRIORITY_MARKERS.values())
484
+ has_markers = any(
485
+ marker in content for marker in self.PRIORITY_MARKERS.values()
486
+ )
468
487
  if not has_markers:
469
488
  issues.append("No priority markers found (🔴🟡🟢⚪)")
470
489
 
@@ -533,4 +552,4 @@ class DocumentationManager:
533
552
  - **Last Updated**: {datetime.now().isoformat()}
534
553
  - **Created By**: Claude MPM /mpm-init
535
554
  - **Update Frequency**: As needed when requirements change
536
- """
555
+ """