claude-mpm 5.1.9__py3-none-any.whl → 5.4.48__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +4 -0
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +843 -900
- claude_mpm/agents/WORKFLOW.md +5 -254
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +2 -2
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +18 -27
- claude_mpm/cli/commands/agents.py +9 -40
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1098 -159
- claude_mpm/cli/commands/configure_agent_display.py +25 -6
- claude_mpm/cli/commands/mpm_init/core.py +225 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/postmortem.py +1 -1
- claude_mpm/cli/commands/profile.py +277 -0
- claude_mpm/cli/commands/skills.py +218 -197
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +21 -3
- claude_mpm/cli/interactive/agent_wizard.py +2 -2
- claude_mpm/cli/parsers/agents_parser.py +0 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
- claude_mpm/cli/parsers/base_parser.py +12 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/profile_parser.py +148 -0
- claude_mpm/cli/parsers/skills_parser.py +0 -5
- claude_mpm/cli/startup.py +876 -149
- claude_mpm/commands/mpm-config.md +28 -0
- claude_mpm/commands/mpm-doctor.md +9 -22
- claude_mpm/commands/mpm-help.md +5 -287
- claude_mpm/commands/mpm-init.md +81 -507
- claude_mpm/commands/mpm-monitor.md +15 -402
- claude_mpm/commands/mpm-organize.md +120 -0
- claude_mpm/commands/mpm-postmortem.md +6 -108
- claude_mpm/commands/mpm-session-resume.md +12 -363
- claude_mpm/commands/mpm-status.md +5 -69
- claude_mpm/commands/mpm-ticket-view.md +52 -495
- claude_mpm/commands/mpm-version.md +5 -107
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/config.py +2 -4
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/optimized_startup.py +59 -0
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +5 -15
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
- claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
- claude_mpm/dashboard/static/svelte-build/index.html +36 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +63 -19
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/cache_git_manager.py +6 -6
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -5
- claude_mpm/services/agents/deployment/agent_format_converter.py +23 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +32 -20
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +247 -35
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +392 -87
- claude_mpm/services/agents/git_source_manager.py +53 -4
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/recommender.py +5 -3
- claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
- claude_mpm/services/agents/sources/git_source_sync_service.py +120 -7
- claude_mpm/services/agents/startup_sync.py +22 -2
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +101 -16
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/management/lifecycle.py +8 -1
- claude_mpm/services/monitor/server.py +698 -22
- claude_mpm/services/pm_skills_deployer.py +711 -0
- claude_mpm/services/profile_manager.py +331 -0
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +130 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +127 -9
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +51 -6
- claude_mpm/services/socketio/server/core.py +386 -108
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/skills/skill_manager.py +92 -3
- claude_mpm/utils/agent_dependency_loader.py +14 -2
- claude_mpm/utils/agent_filters.py +17 -44
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +47 -3
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/METADATA +53 -87
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/RECORD +157 -197
- claude_mpm-5.4.48.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.48.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.48.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Project Knowledge Extractor for Enhanced /mpm-init Update Mode
|
|
3
|
+
==============================================================
|
|
4
|
+
|
|
5
|
+
This module extracts project knowledge from multiple sources:
|
|
6
|
+
- Git history (architectural decisions, tech stack changes, workflows)
|
|
7
|
+
- Session logs (.claude-mpm/responses/*.json)
|
|
8
|
+
- Memory files (.claude-mpm/memories/*.md)
|
|
9
|
+
|
|
10
|
+
Used to enhance CLAUDE.md updates with accumulated project insights.
|
|
11
|
+
|
|
12
|
+
Author: Claude MPM Development Team
|
|
13
|
+
Created: 2025-12-13
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
import subprocess
|
|
19
|
+
from collections import Counter
|
|
20
|
+
from datetime import datetime, timedelta, timezone
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Dict, List
|
|
23
|
+
|
|
24
|
+
from claude_mpm.core.logging_utils import get_logger
|
|
25
|
+
|
|
26
|
+
logger = get_logger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ProjectKnowledgeExtractor:
|
|
30
|
+
"""Extract project knowledge from git, logs, and memory files."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, project_path: Path):
|
|
33
|
+
"""
|
|
34
|
+
Initialize knowledge extractor.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
project_path: Path to the project root directory
|
|
38
|
+
"""
|
|
39
|
+
self.project_path = project_path
|
|
40
|
+
self.claude_mpm_dir = project_path / ".claude-mpm"
|
|
41
|
+
self.is_git_repo = (project_path / ".git").is_dir()
|
|
42
|
+
|
|
43
|
+
def extract_all(self, days: int = 90) -> Dict[str, Any]:
|
|
44
|
+
"""
|
|
45
|
+
Extract knowledge from all sources.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
days: Number of days to analyze git history (default: 90)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dict containing all extracted knowledge
|
|
52
|
+
"""
|
|
53
|
+
return {
|
|
54
|
+
"git_insights": self.extract_from_git(days),
|
|
55
|
+
"log_insights": self.extract_from_logs(),
|
|
56
|
+
"memory_insights": self.extract_from_memory(),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def extract_from_git(self, days: int = 90) -> Dict[str, Any]:
|
|
60
|
+
"""
|
|
61
|
+
Extract insights from git history.
|
|
62
|
+
|
|
63
|
+
Focus on:
|
|
64
|
+
- Architectural patterns from commit messages
|
|
65
|
+
- Tech stack changes (new dependencies, migrations)
|
|
66
|
+
- Common workflows (build, test, deploy patterns)
|
|
67
|
+
- Hot files (frequently modified = important)
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
days: Number of days to analyze
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dict with git insights including patterns, workflows, tech changes
|
|
74
|
+
"""
|
|
75
|
+
if not self.is_git_repo:
|
|
76
|
+
return {
|
|
77
|
+
"available": False,
|
|
78
|
+
"message": "Not a git repository",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
insights = {
|
|
82
|
+
"available": True,
|
|
83
|
+
"architectural_decisions": [],
|
|
84
|
+
"tech_stack_changes": [],
|
|
85
|
+
"workflow_patterns": [],
|
|
86
|
+
"hot_files": [],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
# Get recent commits
|
|
91
|
+
since_date = (datetime.now(timezone.utc) - timedelta(days=days)).strftime(
|
|
92
|
+
"%Y-%m-%d"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Get commit messages with file stats
|
|
96
|
+
result = subprocess.run(
|
|
97
|
+
[
|
|
98
|
+
"git",
|
|
99
|
+
"log",
|
|
100
|
+
f"--since={since_date}",
|
|
101
|
+
"--pretty=format:%s|||%b",
|
|
102
|
+
"--stat",
|
|
103
|
+
"--no-merges",
|
|
104
|
+
],
|
|
105
|
+
cwd=self.project_path,
|
|
106
|
+
capture_output=True,
|
|
107
|
+
text=True,
|
|
108
|
+
check=False,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if result.returncode == 0 and result.stdout:
|
|
112
|
+
insights["architectural_decisions"] = (
|
|
113
|
+
self._extract_architectural_patterns(result.stdout)
|
|
114
|
+
)
|
|
115
|
+
insights["tech_stack_changes"] = self._extract_tech_changes(
|
|
116
|
+
result.stdout
|
|
117
|
+
)
|
|
118
|
+
insights["workflow_patterns"] = self._extract_workflow_patterns(
|
|
119
|
+
result.stdout
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Get file change frequency
|
|
123
|
+
freq_result = subprocess.run(
|
|
124
|
+
[
|
|
125
|
+
"git",
|
|
126
|
+
"log",
|
|
127
|
+
f"--since={since_date}",
|
|
128
|
+
"--pretty=format:",
|
|
129
|
+
"--name-only",
|
|
130
|
+
"--no-merges",
|
|
131
|
+
],
|
|
132
|
+
cwd=self.project_path,
|
|
133
|
+
capture_output=True,
|
|
134
|
+
text=True,
|
|
135
|
+
check=False,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if freq_result.returncode == 0 and freq_result.stdout:
|
|
139
|
+
insights["hot_files"] = self._identify_hot_files(freq_result.stdout)
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.warning(f"Failed to extract git insights: {e}")
|
|
143
|
+
insights["error"] = str(e)
|
|
144
|
+
|
|
145
|
+
return insights
|
|
146
|
+
|
|
147
|
+
def extract_from_logs(self) -> Dict[str, Any]:
|
|
148
|
+
"""
|
|
149
|
+
Extract learnings from session logs.
|
|
150
|
+
|
|
151
|
+
Parse .claude-mpm/responses/*.json for:
|
|
152
|
+
- pm_summary fields with completed work
|
|
153
|
+
- tasks arrays showing what was built
|
|
154
|
+
- stop_event data with context
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Dict with extracted learnings from session logs
|
|
158
|
+
"""
|
|
159
|
+
insights = {
|
|
160
|
+
"available": False,
|
|
161
|
+
"learnings": [],
|
|
162
|
+
"completed_tasks": [],
|
|
163
|
+
"common_patterns": [],
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
responses_dir = self.claude_mpm_dir / "responses"
|
|
167
|
+
if not responses_dir.exists():
|
|
168
|
+
return insights
|
|
169
|
+
|
|
170
|
+
insights["available"] = True
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
# Find all JSON response files
|
|
174
|
+
json_files = list(responses_dir.glob("*.json"))
|
|
175
|
+
|
|
176
|
+
for json_file in json_files[:50]: # Limit to 50 most recent
|
|
177
|
+
try:
|
|
178
|
+
with open(json_file, encoding="utf-8") as f:
|
|
179
|
+
data = json.load(f)
|
|
180
|
+
|
|
181
|
+
# Extract PM summaries
|
|
182
|
+
if data.get("pm_summary"):
|
|
183
|
+
insights["learnings"].append(
|
|
184
|
+
{
|
|
185
|
+
"source": "pm_summary",
|
|
186
|
+
"timestamp": json_file.stem,
|
|
187
|
+
"content": data["pm_summary"],
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Extract task information
|
|
192
|
+
if "tasks" in data and isinstance(data["tasks"], list):
|
|
193
|
+
for task in data["tasks"]:
|
|
194
|
+
if isinstance(task, dict) and "description" in task:
|
|
195
|
+
insights["completed_tasks"].append(task["description"])
|
|
196
|
+
|
|
197
|
+
# Extract stop event context
|
|
198
|
+
if "stop_event" in data and isinstance(data["stop_event"], dict):
|
|
199
|
+
stop_event = data["stop_event"]
|
|
200
|
+
if "context" in stop_event:
|
|
201
|
+
insights["learnings"].append(
|
|
202
|
+
{
|
|
203
|
+
"source": "stop_event",
|
|
204
|
+
"timestamp": json_file.stem,
|
|
205
|
+
"content": stop_event["context"],
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.debug(f"Failed to parse {json_file}: {e}")
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
# Identify common patterns in completed tasks
|
|
214
|
+
if insights["completed_tasks"]:
|
|
215
|
+
insights["common_patterns"] = self._identify_task_patterns(
|
|
216
|
+
insights["completed_tasks"]
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.warning(f"Failed to extract log insights: {e}")
|
|
221
|
+
insights["error"] = str(e)
|
|
222
|
+
|
|
223
|
+
return insights
|
|
224
|
+
|
|
225
|
+
def extract_from_memory(self) -> Dict[str, Any]:
|
|
226
|
+
"""
|
|
227
|
+
Extract accumulated knowledge from memory files.
|
|
228
|
+
|
|
229
|
+
Parse .claude-mpm/memories/*.md for:
|
|
230
|
+
- Project Architecture sections
|
|
231
|
+
- Implementation Guidelines
|
|
232
|
+
- Common Mistakes to Avoid
|
|
233
|
+
- Current Technical Context
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Dict with extracted memory insights
|
|
237
|
+
"""
|
|
238
|
+
insights = {
|
|
239
|
+
"available": False,
|
|
240
|
+
"architectural_knowledge": [],
|
|
241
|
+
"implementation_guidelines": [],
|
|
242
|
+
"common_mistakes": [],
|
|
243
|
+
"technical_context": [],
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
memories_dir = self.claude_mpm_dir / "memories"
|
|
247
|
+
if not memories_dir.exists():
|
|
248
|
+
return insights
|
|
249
|
+
|
|
250
|
+
insights["available"] = True
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
# Find all markdown memory files (exclude README)
|
|
254
|
+
memory_files = [
|
|
255
|
+
f for f in memories_dir.glob("*.md") if f.name != "README.md"
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
for memory_file in memory_files:
|
|
259
|
+
try:
|
|
260
|
+
with open(memory_file, encoding="utf-8") as f:
|
|
261
|
+
content = f.read()
|
|
262
|
+
|
|
263
|
+
agent_name = memory_file.stem.replace("_memories", "")
|
|
264
|
+
|
|
265
|
+
# Parse memory sections
|
|
266
|
+
sections = self._parse_memory_sections(content)
|
|
267
|
+
|
|
268
|
+
# Extract by section type
|
|
269
|
+
if "Project Architecture" in sections:
|
|
270
|
+
insights["architectural_knowledge"].extend(
|
|
271
|
+
self._extract_memory_items(
|
|
272
|
+
sections["Project Architecture"], agent_name
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
if "Implementation Guidelines" in sections:
|
|
277
|
+
insights["implementation_guidelines"].extend(
|
|
278
|
+
self._extract_memory_items(
|
|
279
|
+
sections["Implementation Guidelines"], agent_name
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if "Common Mistakes to Avoid" in sections:
|
|
284
|
+
insights["common_mistakes"].extend(
|
|
285
|
+
self._extract_memory_items(
|
|
286
|
+
sections["Common Mistakes to Avoid"], agent_name
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if "Current Technical Context" in sections:
|
|
291
|
+
insights["technical_context"].extend(
|
|
292
|
+
self._extract_memory_items(
|
|
293
|
+
sections["Current Technical Context"], agent_name
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logger.debug(f"Failed to parse {memory_file}: {e}")
|
|
299
|
+
continue
|
|
300
|
+
|
|
301
|
+
except Exception as e:
|
|
302
|
+
logger.warning(f"Failed to extract memory insights: {e}")
|
|
303
|
+
insights["error"] = str(e)
|
|
304
|
+
|
|
305
|
+
return insights
|
|
306
|
+
|
|
307
|
+
# Private helper methods
|
|
308
|
+
|
|
309
|
+
def _extract_architectural_patterns(self, git_log: str) -> List[str]:
|
|
310
|
+
"""Extract architectural decisions from commit messages."""
|
|
311
|
+
patterns = []
|
|
312
|
+
|
|
313
|
+
# Patterns indicating architectural changes
|
|
314
|
+
arch_keywords = [
|
|
315
|
+
r"add(?:ed)?\s+(\w+\s+(?:pattern|architecture|design))",
|
|
316
|
+
r"refactor(?:ed)?\s+to\s+(\w+)",
|
|
317
|
+
r"migrat(?:e|ed)\s+(?:to|from)\s+(\w+)",
|
|
318
|
+
r"implement(?:ed)?\s+(\w+\s+(?:pattern|architecture))",
|
|
319
|
+
r"introduc(?:e|ed)\s+(\w+\s+(?:layer|service|handler))",
|
|
320
|
+
]
|
|
321
|
+
|
|
322
|
+
for pattern in arch_keywords:
|
|
323
|
+
matches = re.finditer(pattern, git_log, re.IGNORECASE)
|
|
324
|
+
for match in matches:
|
|
325
|
+
decision = match.group(1).strip()
|
|
326
|
+
if decision and decision not in patterns:
|
|
327
|
+
patterns.append(decision)
|
|
328
|
+
|
|
329
|
+
return patterns[:15] # Limit to top 15
|
|
330
|
+
|
|
331
|
+
def _extract_tech_changes(self, git_log: str) -> List[str]:
|
|
332
|
+
"""Extract tech stack changes from commit messages."""
|
|
333
|
+
changes = []
|
|
334
|
+
|
|
335
|
+
# Patterns indicating tech stack changes
|
|
336
|
+
tech_keywords = [
|
|
337
|
+
r"add(?:ed)?\s+(?:dependency|package|library):\s*(\w+)",
|
|
338
|
+
r"upgrad(?:e|ed)\s+(\w+)\s+(?:to|from)",
|
|
339
|
+
r"switch(?:ed)?\s+(?:to|from)\s+(\w+)",
|
|
340
|
+
r"replac(?:e|ed)\s+(\w+)\s+with\s+(\w+)",
|
|
341
|
+
r"remov(?:e|ed)\s+(\w+)\s+dependency",
|
|
342
|
+
]
|
|
343
|
+
|
|
344
|
+
for pattern in tech_keywords:
|
|
345
|
+
matches = re.finditer(pattern, git_log, re.IGNORECASE)
|
|
346
|
+
for match in matches:
|
|
347
|
+
# Get first captured group
|
|
348
|
+
change = match.group(1).strip() if match.group(1) else ""
|
|
349
|
+
if change and change not in changes:
|
|
350
|
+
changes.append(change)
|
|
351
|
+
|
|
352
|
+
return changes[:15] # Limit to top 15
|
|
353
|
+
|
|
354
|
+
def _extract_workflow_patterns(self, git_log: str) -> List[str]:
|
|
355
|
+
"""Extract common workflows from commit messages."""
|
|
356
|
+
workflows = []
|
|
357
|
+
|
|
358
|
+
# Patterns indicating workflows
|
|
359
|
+
workflow_keywords = [
|
|
360
|
+
r"(?:build|test|deploy|lint|format):\s+(.+?)(?:\n|$)",
|
|
361
|
+
r"add(?:ed)?\s+(?:script|command)\s+(?:for|to)\s+(.+?)(?:\n|$)",
|
|
362
|
+
r"automat(?:e|ed)\s+(.+?)(?:\n|$)",
|
|
363
|
+
r"(?:ci|cd):\s+(.+?)(?:\n|$)",
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
for pattern in workflow_keywords:
|
|
367
|
+
matches = re.finditer(pattern, git_log, re.IGNORECASE)
|
|
368
|
+
for match in matches:
|
|
369
|
+
workflow = match.group(1).strip()
|
|
370
|
+
if workflow and len(workflow) < 100 and workflow not in workflows:
|
|
371
|
+
workflows.append(workflow)
|
|
372
|
+
|
|
373
|
+
return workflows[:10] # Limit to top 10
|
|
374
|
+
|
|
375
|
+
def _identify_hot_files(self, file_list: str) -> List[Dict[str, Any]]:
|
|
376
|
+
"""Identify frequently modified files (hot spots)."""
|
|
377
|
+
# Count file modifications
|
|
378
|
+
files = [f.strip() for f in file_list.split("\n") if f.strip()]
|
|
379
|
+
file_counts = Counter(files)
|
|
380
|
+
|
|
381
|
+
# Return top 20 most modified files
|
|
382
|
+
hot_files = []
|
|
383
|
+
for file_path, count in file_counts.most_common(20):
|
|
384
|
+
# Skip certain files
|
|
385
|
+
if any(
|
|
386
|
+
skip in file_path
|
|
387
|
+
for skip in [".lock", "package-lock", "poetry.lock", ".min."]
|
|
388
|
+
):
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
hot_files.append(
|
|
392
|
+
{
|
|
393
|
+
"path": file_path,
|
|
394
|
+
"modifications": count,
|
|
395
|
+
}
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
return hot_files
|
|
399
|
+
|
|
400
|
+
def _identify_task_patterns(self, tasks: List[str]) -> List[str]:
|
|
401
|
+
"""Identify common patterns in completed tasks."""
|
|
402
|
+
# Extract common words/phrases
|
|
403
|
+
words = []
|
|
404
|
+
for task in tasks:
|
|
405
|
+
# Extract keywords (simple approach)
|
|
406
|
+
task_words = re.findall(r"\b[a-z]{4,}\b", task.lower())
|
|
407
|
+
words.extend(task_words)
|
|
408
|
+
|
|
409
|
+
# Count word frequency
|
|
410
|
+
word_counts = Counter(words)
|
|
411
|
+
|
|
412
|
+
# Return top 10 most common (excluding stopwords)
|
|
413
|
+
stopwords = {
|
|
414
|
+
"with",
|
|
415
|
+
"from",
|
|
416
|
+
"this",
|
|
417
|
+
"that",
|
|
418
|
+
"have",
|
|
419
|
+
"been",
|
|
420
|
+
"were",
|
|
421
|
+
"will",
|
|
422
|
+
"their",
|
|
423
|
+
"about",
|
|
424
|
+
}
|
|
425
|
+
patterns = [
|
|
426
|
+
word
|
|
427
|
+
for word, _count in word_counts.most_common(20)
|
|
428
|
+
if word not in stopwords
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
return patterns[:10]
|
|
432
|
+
|
|
433
|
+
def _parse_memory_sections(self, content: str) -> Dict[str, str]:
|
|
434
|
+
"""Parse markdown memory file into sections."""
|
|
435
|
+
sections = {}
|
|
436
|
+
current_section = None
|
|
437
|
+
current_content = []
|
|
438
|
+
|
|
439
|
+
for line in content.split("\n"):
|
|
440
|
+
# Check for section headers (## Section Name)
|
|
441
|
+
if line.startswith("## "):
|
|
442
|
+
# Save previous section
|
|
443
|
+
if current_section:
|
|
444
|
+
sections[current_section] = "\n".join(current_content).strip()
|
|
445
|
+
|
|
446
|
+
# Start new section
|
|
447
|
+
current_section = line[3:].strip()
|
|
448
|
+
current_content = []
|
|
449
|
+
elif current_section:
|
|
450
|
+
current_content.append(line)
|
|
451
|
+
|
|
452
|
+
# Save last section
|
|
453
|
+
if current_section:
|
|
454
|
+
sections[current_section] = "\n".join(current_content).strip()
|
|
455
|
+
|
|
456
|
+
return sections
|
|
457
|
+
|
|
458
|
+
def _extract_memory_items(self, section_content: str, agent_name: str) -> List[str]:
|
|
459
|
+
"""Extract individual items from memory section."""
|
|
460
|
+
items = []
|
|
461
|
+
|
|
462
|
+
# Split by bullet points or numbered lists
|
|
463
|
+
lines = section_content.split("\n")
|
|
464
|
+
for line in lines:
|
|
465
|
+
line = line.strip()
|
|
466
|
+
|
|
467
|
+
# Match bullet points or numbered lists
|
|
468
|
+
if line.startswith(("-", "*", "•")) or re.match(r"^\d+\.", line):
|
|
469
|
+
# Remove bullet/number
|
|
470
|
+
item = re.sub(r"^[-*•]\s*", "", line)
|
|
471
|
+
item = re.sub(r"^\d+\.\s*", "", item)
|
|
472
|
+
item = item.strip()
|
|
473
|
+
|
|
474
|
+
if item:
|
|
475
|
+
# Prefix with agent name for context
|
|
476
|
+
items.append(f"[{agent_name}] {item}")
|
|
477
|
+
|
|
478
|
+
return items
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
__all__ = ["ProjectKnowledgeExtractor"]
|