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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/WORKFLOW.md +2 -14
- claude_mpm/agents/agent_loader.py +3 -2
- claude_mpm/agents/agent_loader_integration.py +2 -1
- claude_mpm/agents/async_agent_loader.py +2 -2
- claude_mpm/agents/base_agent_loader.py +2 -2
- claude_mpm/agents/frontmatter_validator.py +1 -0
- claude_mpm/agents/system_agent_config.py +2 -1
- claude_mpm/cli/commands/configure.py +2 -29
- claude_mpm/cli/commands/doctor.py +44 -5
- claude_mpm/cli/commands/mpm_init.py +117 -63
- claude_mpm/cli/parsers/configure_parser.py +6 -15
- claude_mpm/cli/startup_logging.py +1 -3
- claude_mpm/config/agent_config.py +1 -1
- claude_mpm/config/paths.py +2 -1
- claude_mpm/core/agent_name_normalizer.py +1 -0
- claude_mpm/core/config.py +2 -1
- claude_mpm/core/config_aliases.py +2 -1
- claude_mpm/core/file_utils.py +0 -1
- claude_mpm/core/framework/__init__.py +38 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +367 -0
- claude_mpm/core/framework/formatters/content_formatter.py +288 -0
- claude_mpm/core/framework/formatters/context_generator.py +184 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +206 -0
- claude_mpm/core/framework/loaders/file_loader.py +223 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +161 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +230 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +244 -0
- claude_mpm/core/framework_loader.py +298 -1795
- claude_mpm/core/log_manager.py +2 -1
- claude_mpm/core/tool_access_control.py +1 -0
- claude_mpm/core/unified_agent_registry.py +2 -1
- claude_mpm/core/unified_paths.py +1 -0
- claude_mpm/experimental/cli_enhancements.py +1 -0
- claude_mpm/hooks/__init__.py +9 -1
- claude_mpm/hooks/base_hook.py +1 -0
- claude_mpm/hooks/instruction_reinforcement.py +1 -0
- claude_mpm/hooks/kuzu_memory_hook.py +359 -0
- claude_mpm/hooks/validation_hooks.py +1 -1
- claude_mpm/scripts/mpm_doctor.py +1 -0
- claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
- claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
- claude_mpm/services/agents/management/agent_management_service.py +1 -1
- claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
- claude_mpm/services/agents/memory/memory_file_service.py +6 -2
- claude_mpm/services/agents/memory/memory_format_service.py +0 -1
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
- claude_mpm/services/async_session_logger.py +1 -1
- claude_mpm/services/claude_session_logger.py +1 -0
- claude_mpm/services/core/path_resolver.py +2 -0
- claude_mpm/services/diagnostics/checks/__init__.py +2 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +399 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +4 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
- claude_mpm/services/event_bus/direct_relay.py +2 -1
- claude_mpm/services/event_bus/event_bus.py +1 -0
- claude_mpm/services/event_bus/relay.py +3 -2
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
- claude_mpm/services/infrastructure/daemon_manager.py +1 -1
- claude_mpm/services/mcp_config_manager.py +67 -4
- claude_mpm/services/mcp_gateway/core/process_pool.py +320 -0
- claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +3 -13
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
- claude_mpm/services/mcp_gateway/tools/__init__.py +14 -2
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +38 -6
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +527 -0
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/project/archive_manager.py +159 -96
- claude_mpm/services/project/documentation_manager.py +64 -45
- claude_mpm/services/project/enhanced_analyzer.py +132 -89
- claude_mpm/services/project/project_organizer.py +225 -131
- claude_mpm/services/response_tracker.py +1 -1
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
- claude_mpm/services/unified/__init__.py +1 -1
- claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
- claude_mpm/services/unified/config_strategies/__init__.py +175 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +735 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +750 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1009 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +879 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +814 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1144 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
- claude_mpm/services/unified/deployment_strategies/base.py +24 -28
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
- claude_mpm/services/unified/deployment_strategies/local.py +49 -34
- claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
- claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
- claude_mpm/services/unified/interfaces.py +0 -26
- claude_mpm/services/unified/migration.py +17 -40
- claude_mpm/services/unified/strategies.py +9 -26
- claude_mpm/services/unified/unified_analyzer.py +48 -44
- claude_mpm/services/unified/unified_config.py +21 -19
- claude_mpm/services/unified/unified_deployment.py +21 -26
- claude_mpm/storage/state_storage.py +1 -0
- claude_mpm/utils/agent_dependency_loader.py +18 -6
- claude_mpm/utils/common.py +14 -12
- claude_mpm/utils/database_connector.py +15 -12
- claude_mpm/utils/error_handler.py +1 -0
- claude_mpm/utils/log_cleanup.py +1 -0
- claude_mpm/utils/path_operations.py +1 -0
- claude_mpm/utils/session_logging.py +1 -1
- claude_mpm/utils/subprocess_utils.py +1 -0
- claude_mpm/validation/agent_validator.py +1 -1
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +23 -17
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +126 -105
- claude_mpm/cli/commands/configure_tui.py +0 -1927
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
- {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
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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(
|
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,
|
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: {
|
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: {
|
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
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
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
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
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
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
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
|
-
|
528
|
-
|
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
|
-
|
566
|
-
|
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
|
-
|
596
|
-
|
597
|
-
|
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(
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
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
|
-
|
653
|
-
|
654
|
-
|
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(
|
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
|
-
|
664
|
-
|
665
|
-
|
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
|
-
|
677
|
-
|
678
|
-
|
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
|
-
|
683
|
-
|
684
|
-
|
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(
|
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(
|
712
|
-
|
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(
|
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(
|
721
|
-
|
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
|
-
"
|
778
|
-
}
|
779
|
-
|
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(
|
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(
|
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(
|
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: {
|
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 =
|
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(
|
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(
|
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,
|
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(
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
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(
|
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(
|
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(
|
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
|
-
|
297
|
+
if "critical" in title and "security" in title:
|
283
298
|
return "critical_security"
|
284
|
-
|
299
|
+
if "critical" in title and "business" in title:
|
285
300
|
return "critical_business"
|
286
|
-
|
301
|
+
if "important" in title and "architecture" in title:
|
287
302
|
return "important_architecture"
|
288
|
-
|
303
|
+
if "important" in title and "workflow" in title:
|
289
304
|
return "important_workflow"
|
290
|
-
|
305
|
+
if "project" in title and "overview" in title:
|
291
306
|
return "project_overview"
|
292
|
-
|
307
|
+
if "standard" in title and "coding" in title:
|
293
308
|
return "standard_coding"
|
294
|
-
|
309
|
+
if "standard" in title and "tasks" in title:
|
295
310
|
return "standard_tasks"
|
296
|
-
|
311
|
+
if "documentation" in title:
|
297
312
|
return "documentation_links"
|
298
|
-
|
313
|
+
if "optional" in title or "future" in title:
|
299
314
|
return "optional_future"
|
300
|
-
|
315
|
+
if "meta" in title or "maintain" in title:
|
301
316
|
return "meta_maintenance"
|
302
|
-
|
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
|
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(
|
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(
|
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
|
+
"""
|