claude-mpm 5.1.9__py3-none-any.whl → 5.4.14__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.

Files changed (162) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +85 -0
  4. claude_mpm/agents/agent_loader.py +13 -44
  5. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  6. claude_mpm/cli/__main__.py +4 -0
  7. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  8. claude_mpm/cli/commands/auto_configure.py +210 -25
  9. claude_mpm/cli/commands/config.py +88 -2
  10. claude_mpm/cli/commands/configure.py +1097 -158
  11. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  12. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  13. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  14. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  15. claude_mpm/cli/commands/skills.py +21 -2
  16. claude_mpm/cli/commands/summarize.py +413 -0
  17. claude_mpm/cli/executor.py +11 -3
  18. claude_mpm/cli/parsers/base_parser.py +5 -0
  19. claude_mpm/cli/parsers/config_parser.py +153 -83
  20. claude_mpm/cli/parsers/skills_parser.py +3 -2
  21. claude_mpm/cli/startup.py +333 -89
  22. claude_mpm/commands/mpm-config.md +266 -0
  23. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  24. claude_mpm/config/agent_sources.py +27 -0
  25. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  26. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  27. claude_mpm/core/framework_loader.py +4 -2
  28. claude_mpm/core/logger.py +13 -0
  29. claude_mpm/core/socketio_pool.py +3 -3
  30. claude_mpm/core/unified_agent_registry.py +5 -15
  31. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  32. claude_mpm/hooks/claude_hooks/event_handlers.py +206 -78
  33. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  34. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  35. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  36. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  37. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  38. claude_mpm/hooks/memory_integration_hook.py +46 -1
  39. claude_mpm/init.py +0 -19
  40. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  41. claude_mpm/scripts/launch_monitor.py +93 -13
  42. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  43. claude_mpm/services/agents/agent_review_service.py +280 -0
  44. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  45. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  46. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  47. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
  48. claude_mpm/services/agents/git_source_manager.py +34 -0
  49. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  51. claude_mpm/services/agents/toolchain_detector.py +10 -6
  52. claude_mpm/services/analysis/__init__.py +11 -1
  53. claude_mpm/services/analysis/clone_detector.py +1030 -0
  54. claude_mpm/services/command_deployment_service.py +71 -10
  55. claude_mpm/services/event_bus/config.py +3 -1
  56. claude_mpm/services/git/git_operations_service.py +93 -8
  57. claude_mpm/services/monitor/daemon.py +9 -2
  58. claude_mpm/services/monitor/daemon_manager.py +39 -3
  59. claude_mpm/services/monitor/server.py +225 -19
  60. claude_mpm/services/self_upgrade_service.py +120 -12
  61. claude_mpm/services/skills/__init__.py +3 -0
  62. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  63. claude_mpm/services/skills/selective_skill_deployer.py +230 -0
  64. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  65. claude_mpm/services/skills_deployer.py +64 -3
  66. claude_mpm/services/socketio/event_normalizer.py +15 -1
  67. claude_mpm/services/socketio/server/core.py +160 -21
  68. claude_mpm/services/version_control/git_operations.py +103 -0
  69. claude_mpm/utils/agent_filters.py +17 -44
  70. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/METADATA +47 -84
  71. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/RECORD +76 -150
  72. claude_mpm-5.4.14.dist-info/entry_points.txt +5 -0
  73. claude_mpm-5.4.14.dist-info/licenses/LICENSE +94 -0
  74. claude_mpm-5.4.14.dist-info/licenses/LICENSE-FAQ.md +153 -0
  75. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  76. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  77. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  78. claude_mpm/agents/BASE_OPS.md +0 -219
  79. claude_mpm/agents/BASE_PM.md +0 -480
  80. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  81. claude_mpm/agents/BASE_QA.md +0 -167
  82. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  83. claude_mpm/agents/base_agent.json +0 -31
  84. claude_mpm/agents/base_agent_loader.py +0 -601
  85. claude_mpm/cli/ticket_cli.py +0 -35
  86. claude_mpm/commands/mpm-config-view.md +0 -150
  87. claude_mpm/dashboard/analysis_runner.py +0 -455
  88. claude_mpm/dashboard/index.html +0 -13
  89. claude_mpm/dashboard/open_dashboard.py +0 -66
  90. claude_mpm/dashboard/static/css/activity.css +0 -1958
  91. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  92. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  93. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  94. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  95. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  96. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  97. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  98. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  99. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  100. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  101. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  102. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  103. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  104. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  105. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  106. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  107. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  108. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  109. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  110. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  111. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  112. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  113. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  114. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  115. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  116. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  117. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  118. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  119. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  120. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  121. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  122. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  123. claude_mpm/dashboard/templates/code_simple.html +0 -153
  124. claude_mpm/dashboard/templates/index.html +0 -606
  125. claude_mpm/dashboard/test_dashboard.html +0 -372
  126. claude_mpm/scripts/mcp_server.py +0 -75
  127. claude_mpm/scripts/mcp_wrapper.py +0 -39
  128. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  129. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  130. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  131. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  132. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  133. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  134. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  135. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  136. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  137. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  138. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  139. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  140. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  141. claude_mpm/services/mcp_gateway/main.py +0 -589
  142. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  143. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  144. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  145. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  146. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  147. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  148. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  149. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  150. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  151. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  152. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  153. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  154. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  155. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  156. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  157. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  158. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  159. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  160. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  161. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/WHEEL +0 -0
  162. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.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"]