claude-mpm 5.1.9__py3-none-any.whl → 5.4.3__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 (131) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +46 -0
  3. claude_mpm/agents/agent_loader.py +10 -17
  4. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  5. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  6. claude_mpm/cli/commands/configure.py +1046 -149
  7. claude_mpm/cli/commands/configure_agent_display.py +13 -6
  8. claude_mpm/cli/commands/mpm_init/core.py +158 -1
  9. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  10. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  11. claude_mpm/cli/commands/summarize.py +413 -0
  12. claude_mpm/cli/executor.py +8 -0
  13. claude_mpm/cli/parsers/base_parser.py +5 -0
  14. claude_mpm/cli/startup.py +60 -53
  15. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  16. claude_mpm/config/agent_sources.py +27 -0
  17. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  18. claude_mpm/core/socketio_pool.py +3 -3
  19. claude_mpm/core/unified_agent_registry.py +5 -15
  20. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  21. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  22. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  23. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  24. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  25. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  26. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  27. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  28. claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
  30. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  31. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  32. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  33. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  34. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  35. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  36. claude_mpm/scripts/launch_monitor.py +93 -13
  37. claude_mpm/services/agents/agent_recommendation_service.py +279 -0
  38. claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
  39. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +322 -53
  40. claude_mpm/services/agents/git_source_manager.py +20 -0
  41. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  42. claude_mpm/services/agents/toolchain_detector.py +6 -5
  43. claude_mpm/services/analysis/__init__.py +11 -1
  44. claude_mpm/services/analysis/clone_detector.py +1030 -0
  45. claude_mpm/services/command_deployment_service.py +0 -2
  46. claude_mpm/services/event_bus/config.py +3 -1
  47. claude_mpm/services/monitor/daemon.py +9 -2
  48. claude_mpm/services/monitor/daemon_manager.py +39 -3
  49. claude_mpm/services/monitor/server.py +225 -19
  50. claude_mpm/services/socketio/event_normalizer.py +15 -1
  51. claude_mpm/services/socketio/server/core.py +160 -21
  52. claude_mpm/services/version_control/git_operations.py +103 -0
  53. claude_mpm/utils/agent_filters.py +17 -44
  54. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +1 -77
  55. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +59 -114
  56. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
  57. claude_mpm/dashboard/analysis_runner.py +0 -455
  58. claude_mpm/dashboard/index.html +0 -13
  59. claude_mpm/dashboard/open_dashboard.py +0 -66
  60. claude_mpm/dashboard/static/css/activity.css +0 -1958
  61. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  62. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  63. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  64. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  65. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  66. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  67. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  68. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  69. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  70. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  71. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  72. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  73. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  74. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  75. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  76. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  77. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  78. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  79. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  80. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  81. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  82. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  83. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  84. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  85. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  86. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  87. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  88. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  89. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  90. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  91. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  92. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  93. claude_mpm/dashboard/templates/code_simple.html +0 -153
  94. claude_mpm/dashboard/templates/index.html +0 -606
  95. claude_mpm/dashboard/test_dashboard.html +0 -372
  96. claude_mpm/scripts/mcp_server.py +0 -75
  97. claude_mpm/scripts/mcp_wrapper.py +0 -39
  98. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  99. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  100. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  101. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  102. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  103. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  104. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  105. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  106. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  107. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  108. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  109. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  110. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  111. claude_mpm/services/mcp_gateway/main.py +0 -589
  112. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  113. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  114. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  115. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  116. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  117. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  118. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  119. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  120. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  121. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  122. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  123. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  124. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  125. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  126. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  127. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  128. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  129. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
  130. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
  131. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.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"]