claude-mpm 4.1.2__py3-none-any.whl → 4.1.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/BASE_AGENT_TEMPLATE.md +16 -19
- claude_mpm/agents/MEMORY.md +21 -49
- claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +156 -0
- claude_mpm/agents/templates/api_qa.json +36 -116
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +42 -9
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +29 -6
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +34 -6
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +41 -9
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +30 -8
- claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +2 -2
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +29 -6
- claude_mpm/agents/templates/backup/research_memory_efficient.json +2 -2
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +41 -9
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +23 -7
- claude_mpm/agents/templates/code_analyzer.json +18 -36
- claude_mpm/agents/templates/data_engineer.json +43 -14
- claude_mpm/agents/templates/documentation.json +55 -74
- claude_mpm/agents/templates/engineer.json +57 -40
- claude_mpm/agents/templates/imagemagick.json +7 -2
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +36 -4
- claude_mpm/agents/templates/project_organizer.json +23 -71
- claude_mpm/agents/templates/qa.json +34 -2
- claude_mpm/agents/templates/refactoring_engineer.json +9 -5
- claude_mpm/agents/templates/research.json +36 -4
- claude_mpm/agents/templates/security.json +29 -2
- claude_mpm/agents/templates/ticketing.json +3 -3
- claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
- claude_mpm/agents/templates/version_control.json +28 -2
- claude_mpm/agents/templates/web_qa.json +38 -151
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/commands/agent_manager.py +221 -1
- claude_mpm/cli/commands/agents.py +556 -1009
- claude_mpm/cli/commands/memory.py +248 -927
- claude_mpm/cli/commands/run.py +139 -484
- claude_mpm/cli/parsers/agent_manager_parser.py +34 -0
- claude_mpm/cli/startup_logging.py +76 -0
- claude_mpm/core/agent_registry.py +6 -10
- claude_mpm/core/framework_loader.py +205 -595
- claude_mpm/core/log_manager.py +49 -1
- claude_mpm/core/logging_config.py +2 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
- claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
- claude_mpm/services/agents/memory/memory_file_service.py +103 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
- claude_mpm/services/agents/registry/__init__.py +1 -1
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +407 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +589 -0
- claude_mpm/services/cli/dashboard_launcher.py +424 -0
- claude_mpm/services/cli/memory_crud_service.py +617 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/session_manager.py +513 -0
- claude_mpm/services/cli/socketio_manager.py +498 -0
- claude_mpm/services/cli/startup_checker.py +370 -0
- claude_mpm/services/core/cache_manager.py +311 -0
- claude_mpm/services/core/memory_manager.py +637 -0
- claude_mpm/services/core/path_resolver.py +498 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
- claude_mpm/services/memory/router.py +116 -10
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/RECORD +86 -55
- claude_mpm/cli/commands/run_config_checker.py +0 -159
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Memory Categorization Service - Categorizes learnings into appropriate sections."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MemoryCategorizationService:
|
|
9
|
+
"""Service for categorizing memory learnings."""
|
|
10
|
+
|
|
11
|
+
# Category keywords for automatic categorization
|
|
12
|
+
CATEGORY_KEYWORDS = {
|
|
13
|
+
"Project Architecture": [
|
|
14
|
+
"architecture",
|
|
15
|
+
"structure",
|
|
16
|
+
"design",
|
|
17
|
+
"pattern",
|
|
18
|
+
"framework",
|
|
19
|
+
"component",
|
|
20
|
+
"module",
|
|
21
|
+
"service",
|
|
22
|
+
"interface",
|
|
23
|
+
"api",
|
|
24
|
+
"endpoint",
|
|
25
|
+
"schema",
|
|
26
|
+
"model",
|
|
27
|
+
"database",
|
|
28
|
+
"microservice",
|
|
29
|
+
],
|
|
30
|
+
"Implementation Guidelines": [
|
|
31
|
+
"implement",
|
|
32
|
+
"code",
|
|
33
|
+
"function",
|
|
34
|
+
"method",
|
|
35
|
+
"class",
|
|
36
|
+
"algorithm",
|
|
37
|
+
"logic",
|
|
38
|
+
"process",
|
|
39
|
+
"workflow",
|
|
40
|
+
"feature",
|
|
41
|
+
"requirement",
|
|
42
|
+
"specification",
|
|
43
|
+
"standard",
|
|
44
|
+
"convention",
|
|
45
|
+
"practice",
|
|
46
|
+
],
|
|
47
|
+
"Common Mistakes to Avoid": [
|
|
48
|
+
"mistake",
|
|
49
|
+
"error",
|
|
50
|
+
"bug",
|
|
51
|
+
"issue",
|
|
52
|
+
"problem",
|
|
53
|
+
"avoid",
|
|
54
|
+
"don't",
|
|
55
|
+
"never",
|
|
56
|
+
"warning",
|
|
57
|
+
"caution",
|
|
58
|
+
"gotcha",
|
|
59
|
+
"pitfall",
|
|
60
|
+
"trap",
|
|
61
|
+
"wrong",
|
|
62
|
+
"incorrect",
|
|
63
|
+
],
|
|
64
|
+
"Current Technical Context": [
|
|
65
|
+
"current",
|
|
66
|
+
"status",
|
|
67
|
+
"context",
|
|
68
|
+
"environment",
|
|
69
|
+
"configuration",
|
|
70
|
+
"setup",
|
|
71
|
+
"version",
|
|
72
|
+
"dependency",
|
|
73
|
+
"tool",
|
|
74
|
+
"library",
|
|
75
|
+
"package",
|
|
76
|
+
"integration",
|
|
77
|
+
"deployment",
|
|
78
|
+
"infrastructure",
|
|
79
|
+
"state",
|
|
80
|
+
],
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def __init__(self):
|
|
84
|
+
"""Initialize the categorization service."""
|
|
85
|
+
self.logger = logging.getLogger(__name__)
|
|
86
|
+
|
|
87
|
+
def categorize_learning(self, learning: str) -> str:
|
|
88
|
+
"""Categorize a learning item based on its content.
|
|
89
|
+
|
|
90
|
+
WHY: Learnings are automatically organized into categories to make them
|
|
91
|
+
easier to find and review. This uses keyword matching to determine the
|
|
92
|
+
most appropriate category.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
learning: The learning content to categorize
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Category name (defaults to "Current Technical Context")
|
|
99
|
+
"""
|
|
100
|
+
if not learning:
|
|
101
|
+
return "Current Technical Context"
|
|
102
|
+
|
|
103
|
+
learning_lower = learning.lower()
|
|
104
|
+
category_scores = {}
|
|
105
|
+
|
|
106
|
+
# Score each category based on keyword matches
|
|
107
|
+
for category, keywords in self.CATEGORY_KEYWORDS.items():
|
|
108
|
+
score = sum(1 for keyword in keywords if keyword in learning_lower)
|
|
109
|
+
if score > 0:
|
|
110
|
+
category_scores[category] = score
|
|
111
|
+
|
|
112
|
+
# Return category with highest score, or default
|
|
113
|
+
if category_scores:
|
|
114
|
+
return max(category_scores, key=category_scores.get)
|
|
115
|
+
|
|
116
|
+
return "Current Technical Context"
|
|
117
|
+
|
|
118
|
+
def categorize_learnings_batch(self, learnings: List[str]) -> dict:
|
|
119
|
+
"""Categorize multiple learnings at once.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
learnings: List of learning items to categorize
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Dictionary mapping categories to lists of learnings
|
|
126
|
+
"""
|
|
127
|
+
categorized = {}
|
|
128
|
+
|
|
129
|
+
for learning in learnings:
|
|
130
|
+
category = self.categorize_learning(learning)
|
|
131
|
+
if category not in categorized:
|
|
132
|
+
categorized[category] = []
|
|
133
|
+
categorized[category].append(learning)
|
|
134
|
+
|
|
135
|
+
return categorized
|
|
136
|
+
|
|
137
|
+
def merge_categorized_learnings(
|
|
138
|
+
self, existing: dict, new: dict, max_per_category: int = 15
|
|
139
|
+
) -> dict:
|
|
140
|
+
"""Merge new categorized learnings with existing ones.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
existing: Existing categorized learnings
|
|
144
|
+
new: New categorized learnings to add
|
|
145
|
+
max_per_category: Maximum items per category
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Merged categorized learnings with limits applied
|
|
149
|
+
"""
|
|
150
|
+
merged = existing.copy()
|
|
151
|
+
|
|
152
|
+
for category, items in new.items():
|
|
153
|
+
if category not in merged:
|
|
154
|
+
merged[category] = []
|
|
155
|
+
|
|
156
|
+
# Add new items, avoiding duplicates
|
|
157
|
+
for item in items:
|
|
158
|
+
if item not in merged[category]:
|
|
159
|
+
merged[category].append(item)
|
|
160
|
+
|
|
161
|
+
# Apply limit (keep most recent)
|
|
162
|
+
if len(merged[category]) > max_per_category:
|
|
163
|
+
merged[category] = merged[category][-max_per_category:]
|
|
164
|
+
|
|
165
|
+
return merged
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Memory File Service - Handles file operations for agent memories."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MemoryFileService:
|
|
9
|
+
"""Service for handling memory file operations."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, memories_dir: Path):
|
|
12
|
+
"""Initialize the memory file service.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
memories_dir: Directory where memory files are stored
|
|
16
|
+
"""
|
|
17
|
+
self.memories_dir = memories_dir
|
|
18
|
+
self.logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
def get_memory_file_with_migration(self, directory: Path, agent_id: str) -> Path:
|
|
21
|
+
"""Get memory file path with migration support.
|
|
22
|
+
|
|
23
|
+
Migrates from old naming convention if needed.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
directory: Directory to check for memory file
|
|
27
|
+
agent_id: Agent identifier
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Path to the memory file
|
|
31
|
+
"""
|
|
32
|
+
new_file = directory / f"{agent_id}_memories.md"
|
|
33
|
+
old_file = directory / f"{agent_id}_memory.md"
|
|
34
|
+
|
|
35
|
+
# Migrate from old naming convention if needed
|
|
36
|
+
if old_file.exists() and not new_file.exists():
|
|
37
|
+
try:
|
|
38
|
+
old_file.rename(new_file)
|
|
39
|
+
self.logger.info(f"Migrated memory file: {old_file} -> {new_file}")
|
|
40
|
+
except Exception as e:
|
|
41
|
+
self.logger.warning(f"Could not migrate memory file: {e}")
|
|
42
|
+
return old_file
|
|
43
|
+
|
|
44
|
+
return new_file
|
|
45
|
+
|
|
46
|
+
def save_memory_file(self, file_path: Path, content: str) -> bool:
|
|
47
|
+
"""Save content to a memory file.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
file_path: Path to the memory file
|
|
51
|
+
content: Content to save
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
True if saved successfully, False otherwise
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
# Ensure directory exists
|
|
58
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
|
|
60
|
+
# Write content
|
|
61
|
+
file_path.write_text(content)
|
|
62
|
+
|
|
63
|
+
self.logger.debug(f"Saved memory file: {file_path}")
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
self.logger.error(f"Failed to save memory file {file_path}: {e}")
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def ensure_memories_directory(self) -> None:
|
|
71
|
+
"""Ensure the memories directory exists with README."""
|
|
72
|
+
try:
|
|
73
|
+
# Create directory if it doesn't exist
|
|
74
|
+
self.memories_dir.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
|
|
76
|
+
# Create README if it doesn't exist
|
|
77
|
+
readme_path = self.memories_dir / "README.md"
|
|
78
|
+
if not readme_path.exists():
|
|
79
|
+
readme_content = """# Agent Memories Directory
|
|
80
|
+
|
|
81
|
+
This directory contains memory files for various agents used in the project.
|
|
82
|
+
|
|
83
|
+
## File Format
|
|
84
|
+
|
|
85
|
+
Memory files follow the naming convention: `{agent_id}_memories.md`
|
|
86
|
+
|
|
87
|
+
Each file contains:
|
|
88
|
+
- Agent metadata (name, type, version)
|
|
89
|
+
- Project-specific learnings organized by category
|
|
90
|
+
- Timestamps for tracking updates
|
|
91
|
+
|
|
92
|
+
## Auto-generated
|
|
93
|
+
|
|
94
|
+
These files are managed automatically by the agent memory system.
|
|
95
|
+
Manual edits should be done carefully to preserve the format.
|
|
96
|
+
"""
|
|
97
|
+
readme_path.write_text(readme_content)
|
|
98
|
+
self.logger.debug(
|
|
99
|
+
f"Created README in memories directory: {readme_path}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
self.logger.error(f"Failed to ensure memories directory: {e}")
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Memory Format Service - Handles memory content formatting and parsing."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Dict, List
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MemoryFormatService:
|
|
11
|
+
"""Service for memory content formatting and parsing."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initialize the memory format service."""
|
|
15
|
+
self.logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
def build_simple_memory_content(self, agent_id: str, items: List[str]) -> str:
|
|
18
|
+
"""Build memory content as a simple list with header and timestamp.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
agent_id: Agent identifier for the header
|
|
22
|
+
items: List of memory items
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Formatted memory content string
|
|
26
|
+
"""
|
|
27
|
+
# Build header
|
|
28
|
+
header = f"# {agent_id.title()} Agent Memory\n\n"
|
|
29
|
+
header += f"Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
|
30
|
+
header += "## Learnings\n\n"
|
|
31
|
+
|
|
32
|
+
# Build item list
|
|
33
|
+
item_lines = []
|
|
34
|
+
for item in items:
|
|
35
|
+
# Clean and format each item
|
|
36
|
+
item = item.strip()
|
|
37
|
+
if item and not item.startswith("- "):
|
|
38
|
+
item = f"- {item}"
|
|
39
|
+
if item:
|
|
40
|
+
item_lines.append(item)
|
|
41
|
+
|
|
42
|
+
# Combine
|
|
43
|
+
content = header + "\n".join(item_lines)
|
|
44
|
+
if item_lines:
|
|
45
|
+
content += "\n"
|
|
46
|
+
|
|
47
|
+
return content
|
|
48
|
+
|
|
49
|
+
def parse_memory_list(self, memory_content: str) -> List[str]:
|
|
50
|
+
"""Parse memory content into a simple list.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
memory_content: Raw memory content
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
List of memory items (without "- " prefix)
|
|
57
|
+
"""
|
|
58
|
+
items = []
|
|
59
|
+
lines = memory_content.split("\n")
|
|
60
|
+
|
|
61
|
+
for line in lines:
|
|
62
|
+
line = line.strip()
|
|
63
|
+
# Skip headers, empty lines, and metadata
|
|
64
|
+
if (
|
|
65
|
+
not line
|
|
66
|
+
or line.startswith("#")
|
|
67
|
+
or line.startswith("Last Updated:")
|
|
68
|
+
or line.startswith("**")
|
|
69
|
+
or line == "---"
|
|
70
|
+
):
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
# Extract list items
|
|
74
|
+
if line.startswith("- "):
|
|
75
|
+
item = line[2:].strip()
|
|
76
|
+
if item:
|
|
77
|
+
items.append(item)
|
|
78
|
+
elif line and not any(
|
|
79
|
+
line.startswith(p) for p in ["#", "**", "Last Updated:", "---"]
|
|
80
|
+
):
|
|
81
|
+
# Include non-list items that aren't headers
|
|
82
|
+
items.append(line)
|
|
83
|
+
|
|
84
|
+
return items
|
|
85
|
+
|
|
86
|
+
def parse_memory_sections(self, memory_content: str) -> Dict[str, List[str]]:
|
|
87
|
+
"""Parse memory content into sections and items.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
memory_content: Raw memory file content
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dict mapping section names to lists of items
|
|
94
|
+
"""
|
|
95
|
+
sections = {}
|
|
96
|
+
current_section = None
|
|
97
|
+
current_items = []
|
|
98
|
+
|
|
99
|
+
for line in memory_content.split("\n"):
|
|
100
|
+
# Check for section header (## Section Name)
|
|
101
|
+
if line.startswith("## "):
|
|
102
|
+
# Save previous section if exists
|
|
103
|
+
if current_section and current_items:
|
|
104
|
+
sections[current_section] = current_items
|
|
105
|
+
# Start new section
|
|
106
|
+
current_section = line[3:].strip()
|
|
107
|
+
current_items = []
|
|
108
|
+
# Check for list item
|
|
109
|
+
elif line.startswith("- ") and current_section:
|
|
110
|
+
item = line[2:].strip()
|
|
111
|
+
if item:
|
|
112
|
+
current_items.append(item)
|
|
113
|
+
|
|
114
|
+
# Save last section
|
|
115
|
+
if current_section and current_items:
|
|
116
|
+
sections[current_section] = current_items
|
|
117
|
+
|
|
118
|
+
return sections
|
|
119
|
+
|
|
120
|
+
def clean_template_placeholders(
|
|
121
|
+
self,
|
|
122
|
+
content: str,
|
|
123
|
+
preserve_structure: bool = False,
|
|
124
|
+
agent_id: str = "agent",
|
|
125
|
+
) -> str:
|
|
126
|
+
"""Clean template placeholders from memory content.
|
|
127
|
+
|
|
128
|
+
WHY: Default templates contain placeholder text that should be removed
|
|
129
|
+
when adding real memories. This method cleans those placeholders while
|
|
130
|
+
preserving any actual content.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
content: Memory content to clean
|
|
134
|
+
preserve_structure: If True, preserve empty sections
|
|
135
|
+
agent_id: Agent ID for context
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Cleaned content string
|
|
139
|
+
"""
|
|
140
|
+
if not content:
|
|
141
|
+
return content
|
|
142
|
+
|
|
143
|
+
# Patterns to remove
|
|
144
|
+
placeholder_patterns = [
|
|
145
|
+
r"\[Agent will add.*?\]",
|
|
146
|
+
r"\[No .* yet\]",
|
|
147
|
+
r"<!-- .* -->",
|
|
148
|
+
r"No items yet.*",
|
|
149
|
+
r"Memory items will be added.*",
|
|
150
|
+
r"\*\*Note:.*?\*\*",
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
cleaned = content
|
|
154
|
+
for pattern in placeholder_patterns:
|
|
155
|
+
cleaned = re.sub(pattern, "", cleaned, flags=re.IGNORECASE | re.MULTILINE)
|
|
156
|
+
|
|
157
|
+
if not preserve_structure:
|
|
158
|
+
# Remove empty sections
|
|
159
|
+
lines = []
|
|
160
|
+
skip_empty = False
|
|
161
|
+
for line in cleaned.split("\n"):
|
|
162
|
+
if line.startswith("## "):
|
|
163
|
+
skip_empty = True
|
|
164
|
+
lines.append(line)
|
|
165
|
+
elif line.strip() and skip_empty:
|
|
166
|
+
skip_empty = False
|
|
167
|
+
lines.append(line)
|
|
168
|
+
elif not skip_empty:
|
|
169
|
+
lines.append(line)
|
|
170
|
+
|
|
171
|
+
cleaned = "\n".join(lines)
|
|
172
|
+
|
|
173
|
+
# Clean up multiple blank lines
|
|
174
|
+
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
|
|
175
|
+
|
|
176
|
+
return cleaned.strip()
|
|
177
|
+
|
|
178
|
+
def clean_template_placeholders_list(self, items: List[str]) -> List[str]:
|
|
179
|
+
"""Clean template placeholders from a list of items.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
items: List of items to clean
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Cleaned list of items
|
|
186
|
+
"""
|
|
187
|
+
cleaned = []
|
|
188
|
+
for item in items:
|
|
189
|
+
# Skip placeholder items
|
|
190
|
+
if any(
|
|
191
|
+
pattern in item.lower()
|
|
192
|
+
for pattern in [
|
|
193
|
+
"[agent will add",
|
|
194
|
+
"[no ",
|
|
195
|
+
"no items yet",
|
|
196
|
+
"memory items will be added",
|
|
197
|
+
]
|
|
198
|
+
):
|
|
199
|
+
continue
|
|
200
|
+
cleaned.append(item)
|
|
201
|
+
return cleaned
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Memory Limits Service - Manages memory size limits and configuration."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from claude_mpm.core.config import Config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MemoryLimitsService:
|
|
11
|
+
"""Service for managing memory limits and configuration."""
|
|
12
|
+
|
|
13
|
+
# Default limits
|
|
14
|
+
DEFAULT_MEMORY_LIMITS = {
|
|
15
|
+
"max_file_size_kb": 80, # 80KB (20k tokens)
|
|
16
|
+
"max_items": 100, # Maximum total memory items
|
|
17
|
+
"max_line_length": 120,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def __init__(self, config: Optional[Config] = None):
|
|
21
|
+
"""Initialize the memory limits service.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
config: Optional Config object for reading configuration
|
|
25
|
+
"""
|
|
26
|
+
self.config = config or Config()
|
|
27
|
+
self.logger = logging.getLogger(__name__)
|
|
28
|
+
self.memory_limits = self._init_memory_limits()
|
|
29
|
+
|
|
30
|
+
def _init_memory_limits(self) -> Dict[str, Any]:
|
|
31
|
+
"""Initialize memory limits from configuration.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Dictionary of memory limits
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
limits = self.DEFAULT_MEMORY_LIMITS.copy()
|
|
38
|
+
|
|
39
|
+
# Try to load from config
|
|
40
|
+
if hasattr(self.config, "agent_memory_limits"):
|
|
41
|
+
config_limits = self.config.agent_memory_limits
|
|
42
|
+
if isinstance(config_limits, dict):
|
|
43
|
+
limits.update(config_limits)
|
|
44
|
+
|
|
45
|
+
self.logger.debug(f"Initialized memory limits: {limits}")
|
|
46
|
+
return limits
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
self.logger.warning(f"Failed to load memory limits from config: {e}")
|
|
50
|
+
return self.DEFAULT_MEMORY_LIMITS.copy()
|
|
51
|
+
|
|
52
|
+
def get_agent_limits(self, agent_id: str) -> Dict[str, Any]:
|
|
53
|
+
"""Get memory limits for a specific agent.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
agent_id: Agent identifier
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dictionary of memory limits for the agent
|
|
60
|
+
"""
|
|
61
|
+
# Start with default limits
|
|
62
|
+
limits = self.memory_limits.copy()
|
|
63
|
+
|
|
64
|
+
# Check for agent-specific overrides
|
|
65
|
+
try:
|
|
66
|
+
if hasattr(self.config, "agents") and agent_id in self.config.agents:
|
|
67
|
+
agent_config = self.config.agents[agent_id]
|
|
68
|
+
if "memory_limits" in agent_config:
|
|
69
|
+
limits.update(agent_config["memory_limits"])
|
|
70
|
+
except Exception as e:
|
|
71
|
+
self.logger.debug(f"No agent-specific limits for {agent_id}: {e}")
|
|
72
|
+
|
|
73
|
+
return limits
|
|
74
|
+
|
|
75
|
+
def get_agent_auto_learning(self, agent_id: str) -> bool:
|
|
76
|
+
"""Get auto-learning setting for a specific agent.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
agent_id: Agent identifier
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
True if auto-learning is enabled, False otherwise
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
# Check agent-specific config
|
|
86
|
+
if hasattr(self.config, "agents") and agent_id in self.config.agents:
|
|
87
|
+
agent_config = self.config.agents[agent_id]
|
|
88
|
+
if "auto_learning" in agent_config:
|
|
89
|
+
return agent_config["auto_learning"]
|
|
90
|
+
|
|
91
|
+
# Check global config
|
|
92
|
+
if hasattr(self.config, "agent_auto_learning"):
|
|
93
|
+
return self.config.agent_auto_learning
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
self.logger.debug(f"Error checking auto-learning for {agent_id}: {e}")
|
|
97
|
+
|
|
98
|
+
# Default to True (auto-learning enabled)
|
|
99
|
+
return True
|
|
@@ -4,8 +4,8 @@ from claude_mpm.core.unified_agent_registry import (
|
|
|
4
4
|
AgentMetadata,
|
|
5
5
|
AgentTier,
|
|
6
6
|
AgentType,
|
|
7
|
+
UnifiedAgentRegistry as AgentRegistry,
|
|
7
8
|
)
|
|
8
|
-
from claude_mpm.core.unified_agent_registry import UnifiedAgentRegistry as AgentRegistry
|
|
9
9
|
|
|
10
10
|
from .deployed_agent_discovery import DeployedAgentDiscovery
|
|
11
11
|
from .modification_tracker import (
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""CLI services package.
|
|
2
|
+
|
|
3
|
+
Services specifically for CLI command support and utilities.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .agent_dependency_service import AgentDependencyService, IAgentDependencyService
|
|
7
|
+
from .agent_validation_service import AgentValidationService, IAgentValidationService
|
|
8
|
+
from .startup_checker import IStartupChecker, StartupCheckerService, StartupWarning
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"AgentDependencyService",
|
|
12
|
+
"AgentValidationService",
|
|
13
|
+
"IAgentDependencyService",
|
|
14
|
+
"IAgentValidationService",
|
|
15
|
+
"IStartupChecker",
|
|
16
|
+
"StartupCheckerService",
|
|
17
|
+
"StartupWarning",
|
|
18
|
+
]
|