claude-mpm 4.6.1__py3-none-any.whl → 4.7.1__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.
Files changed (111) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +206 -48
  3. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +787 -0
  4. claude_mpm/agents/base_agent_loader.py +3 -1
  5. claude_mpm/agents/templates/engineer.json +10 -4
  6. claude_mpm/agents/templates/prompt-engineer.json +517 -87
  7. claude_mpm/cli/commands/cleanup.py +1 -1
  8. claude_mpm/cli/commands/mcp_setup_external.py +2 -2
  9. claude_mpm/cli/commands/memory.py +1 -1
  10. claude_mpm/cli/commands/mpm_init.py +5 -4
  11. claude_mpm/cli/commands/run.py +4 -4
  12. claude_mpm/cli/shared/argument_patterns.py +18 -11
  13. claude_mpm/cli/shared/base_command.py +1 -1
  14. claude_mpm/config/experimental_features.py +3 -3
  15. claude_mpm/config/socketio_config.py +1 -1
  16. claude_mpm/core/cache.py +2 -2
  17. claude_mpm/core/claude_runner.py +5 -7
  18. claude_mpm/core/container.py +10 -4
  19. claude_mpm/core/file_utils.py +10 -8
  20. claude_mpm/core/framework/formatters/context_generator.py +3 -2
  21. claude_mpm/core/framework/loaders/agent_loader.py +11 -7
  22. claude_mpm/core/injectable_service.py +11 -8
  23. claude_mpm/core/interactive_session.py +5 -4
  24. claude_mpm/core/oneshot_session.py +3 -2
  25. claude_mpm/core/pm_hook_interceptor.py +15 -9
  26. claude_mpm/core/unified_paths.py +6 -5
  27. claude_mpm/dashboard/api/simple_directory.py +16 -17
  28. claude_mpm/hooks/claude_hooks/event_handlers.py +3 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +2 -2
  30. claude_mpm/hooks/claude_hooks/hook_handler_original.py +2 -2
  31. claude_mpm/hooks/claude_hooks/installer.py +10 -10
  32. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -2
  33. claude_mpm/hooks/claude_hooks/services/state_manager.py +3 -2
  34. claude_mpm/hooks/tool_call_interceptor.py +6 -3
  35. claude_mpm/models/agent_session.py +3 -1
  36. claude_mpm/scripts/mcp_server.py +3 -5
  37. claude_mpm/services/agents/agent_builder.py +4 -4
  38. claude_mpm/services/agents/deployment/deployment_type_detector.py +10 -14
  39. claude_mpm/services/agents/deployment/local_template_deployment.py +6 -3
  40. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +15 -11
  41. claude_mpm/services/agents/deployment/system_instructions_deployer.py +9 -6
  42. claude_mpm/services/agents/loading/agent_profile_loader.py +1 -2
  43. claude_mpm/services/agents/memory/agent_memory_manager.py +27 -27
  44. claude_mpm/services/agents/memory/content_manager.py +9 -4
  45. claude_mpm/services/claude_session_logger.py +5 -8
  46. claude_mpm/services/cli/memory_crud_service.py +1 -1
  47. claude_mpm/services/cli/memory_output_formatter.py +1 -1
  48. claude_mpm/services/cli/startup_checker.py +13 -10
  49. claude_mpm/services/cli/unified_dashboard_manager.py +10 -6
  50. claude_mpm/services/command_deployment_service.py +9 -7
  51. claude_mpm/services/core/path_resolver.py +8 -5
  52. claude_mpm/services/diagnostics/checks/agent_check.py +4 -7
  53. claude_mpm/services/diagnostics/checks/installation_check.py +19 -16
  54. claude_mpm/services/diagnostics/checks/mcp_services_check.py +30 -28
  55. claude_mpm/services/diagnostics/checks/startup_log_check.py +5 -3
  56. claude_mpm/services/events/core.py +2 -3
  57. claude_mpm/services/framework_claude_md_generator/content_validator.py +2 -2
  58. claude_mpm/services/hook_installer_service.py +2 -3
  59. claude_mpm/services/hook_service.py +5 -6
  60. claude_mpm/services/mcp_gateway/auto_configure.py +4 -5
  61. claude_mpm/services/mcp_gateway/main.py +7 -4
  62. claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -4
  63. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +1 -2
  64. claude_mpm/services/mcp_service_verifier.py +18 -17
  65. claude_mpm/services/memory/builder.py +1 -2
  66. claude_mpm/services/memory/indexed_memory.py +1 -1
  67. claude_mpm/services/memory/optimizer.py +1 -2
  68. claude_mpm/services/monitor/daemon_manager.py +3 -3
  69. claude_mpm/services/monitor/handlers/file.py +5 -4
  70. claude_mpm/services/monitor/management/lifecycle.py +1 -1
  71. claude_mpm/services/monitor/server.py +14 -12
  72. claude_mpm/services/project/architecture_analyzer.py +5 -5
  73. claude_mpm/services/project/metrics_collector.py +4 -4
  74. claude_mpm/services/project/project_organizer.py +4 -4
  75. claude_mpm/services/project/registry.py +9 -3
  76. claude_mpm/services/shared/config_service_base.py +10 -11
  77. claude_mpm/services/socketio/handlers/file.py +5 -4
  78. claude_mpm/services/socketio/handlers/git.py +7 -7
  79. claude_mpm/services/socketio/server/core.py +10 -10
  80. claude_mpm/services/subprocess_launcher_service.py +5 -10
  81. claude_mpm/services/ticket_services/formatter_service.py +1 -1
  82. claude_mpm/services/ticket_services/validation_service.py +5 -5
  83. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +5 -5
  84. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +4 -4
  85. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +4 -4
  86. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +4 -4
  87. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +4 -4
  88. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +6 -2
  89. claude_mpm/services/unified/config_strategies/unified_config_service.py +24 -13
  90. claude_mpm/services/version_control/conflict_resolution.py +6 -2
  91. claude_mpm/services/version_control/git_operations.py +1 -1
  92. claude_mpm/services/version_control/version_parser.py +1 -1
  93. claude_mpm/storage/state_storage.py +3 -3
  94. claude_mpm/tools/__main__.py +1 -1
  95. claude_mpm/tools/code_tree_analyzer.py +17 -14
  96. claude_mpm/tools/socketio_debug.py +7 -7
  97. claude_mpm/utils/common.py +6 -2
  98. claude_mpm/utils/config_manager.py +9 -3
  99. claude_mpm/utils/database_connector.py +4 -4
  100. claude_mpm/utils/dependency_strategies.py +1 -1
  101. claude_mpm/utils/environment_context.py +3 -2
  102. claude_mpm/utils/file_utils.py +1 -2
  103. claude_mpm/utils/path_operations.py +3 -1
  104. claude_mpm/utils/robust_installer.py +3 -4
  105. claude_mpm/validation/frontmatter_validator.py +4 -4
  106. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.1.dist-info}/METADATA +1 -1
  107. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.1.dist-info}/RECORD +111 -110
  108. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.1.dist-info}/WHEEL +0 -0
  109. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.1.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.1.dist-info}/licenses/LICENSE +0 -0
  111. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,5 @@
1
1
  """Simple directory listing API with proper filtering to match main code explorer"""
2
2
 
3
- import os
4
3
  from pathlib import Path
5
4
 
6
5
  from aiohttp import web
@@ -104,19 +103,19 @@ def has_code_files(directory_path, max_depth=5, current_depth=0):
104
103
  return False
105
104
 
106
105
  try:
107
- for item in os.listdir(directory_path):
106
+ for item in list(Path(directory_path).iterdir()):
108
107
  # Skip hidden files/dirs unless in exceptions
109
108
  if item.startswith(".") and item not in DOTFILE_EXCEPTIONS:
110
109
  continue
111
110
 
112
- item_path = os.path.join(directory_path, item)
111
+ item_path = Path(directory_path) / item
113
112
 
114
- if os.path.isfile(item_path):
113
+ if Path(item_path).is_file():
115
114
  # Check if it's a code file
116
- ext = os.path.splitext(item)[1].lower()
115
+ ext = Path(item).suffix.lower()
117
116
  if ext in CODE_EXTENSIONS:
118
117
  return True
119
- elif os.path.isdir(item_path):
118
+ elif Path(item_path).is_dir():
120
119
  # Skip certain directories
121
120
  if item in SKIP_DIRS or item.endswith(".egg-info"):
122
121
  continue
@@ -153,9 +152,9 @@ def should_show_item(item_name, item_path, is_directory):
153
152
  if not has_code_files(item_path, max_depth=3):
154
153
  # Check if it has any visible subdirectories
155
154
  try:
156
- for subitem in os.listdir(item_path):
157
- subitem_path = os.path.join(item_path, subitem)
158
- if os.path.isdir(subitem_path):
155
+ for subitem in list(Path(item_path).iterdir()):
156
+ subitem_path = Path(item_path) / subitem
157
+ if Path(subitem_path).is_dir():
159
158
  if not subitem.startswith(".") and subitem not in SKIP_DIRS:
160
159
  return True
161
160
  return False
@@ -163,7 +162,7 @@ def should_show_item(item_name, item_path, is_directory):
163
162
  return False
164
163
  else:
165
164
  # For files, check if it's a code file or documentation
166
- ext = os.path.splitext(item_name)[1].lower()
165
+ ext = Path(item_name).suffix.lower()
167
166
  if ext in CODE_EXTENSIONS:
168
167
  return True
169
168
 
@@ -189,12 +188,12 @@ async def list_directory(request):
189
188
  path = request.query.get("path", ".")
190
189
 
191
190
  # Convert to absolute path
192
- abs_path = os.path.abspath(os.path.expanduser(path))
191
+ abs_path = Path(Path(path).resolve().expanduser())
193
192
 
194
193
  result = {
195
194
  "path": abs_path,
196
- "exists": os.path.exists(abs_path),
197
- "is_directory": os.path.isdir(abs_path),
195
+ "exists": Path(abs_path).exists(),
196
+ "is_directory": Path(abs_path).is_dir(),
198
197
  "contents": [],
199
198
  "filtered": True, # Indicate that filtering is applied
200
199
  "filter_info": "Showing only code files and directories with code",
@@ -206,11 +205,11 @@ async def list_directory(request):
206
205
  gitignore_mgr = GitignoreManager()
207
206
 
208
207
  # List all items
209
- items = os.listdir(abs_path)
208
+ items = list(Path(abs_path).iterdir())
210
209
 
211
210
  for item in items:
212
- item_path = os.path.join(abs_path, item)
213
- is_directory = os.path.isdir(item_path)
211
+ item_path = Path(abs_path) / item
212
+ is_directory = Path(item_path).is_dir()
214
213
 
215
214
  # Check if item should be ignored by gitignore
216
215
  if gitignore_mgr.should_ignore(Path(item_path), Path(abs_path)):
@@ -226,7 +225,7 @@ async def list_directory(request):
226
225
  "name": item,
227
226
  "path": item_path,
228
227
  "is_directory": is_directory,
229
- "is_file": os.path.isfile(item_path),
228
+ "is_file": Path(item_path).is_file(),
230
229
  "is_code_file": not is_directory
231
230
  and any(item.endswith(ext) for ext in CODE_EXTENSIONS),
232
231
  }
@@ -10,6 +10,7 @@ import re
10
10
  import subprocess
11
11
  import sys
12
12
  from datetime import datetime, timezone
13
+ from pathlib import Path
13
14
  from typing import Optional
14
15
 
15
16
  # Import tool analysis with fallback for direct execution
@@ -309,7 +310,7 @@ class EventHandlers:
309
310
  """Get git branch for the given directory with caching."""
310
311
  # Use current working directory if not specified
311
312
  if not working_dir:
312
- working_dir = os.getcwd()
313
+ working_dir = Path.cwd()
313
314
 
314
315
  # Check cache first (cache for 30 seconds)
315
316
  current_time = datetime.now(timezone.utc).timestamp()
@@ -325,7 +326,7 @@ class EventHandlers:
325
326
  # Try to get git branch
326
327
  try:
327
328
  # Change to the working directory temporarily
328
- original_cwd = os.getcwd()
329
+ original_cwd = Path.cwd()
329
330
  os.chdir(working_dir)
330
331
 
331
332
  # Run git command to get current branch
@@ -224,7 +224,7 @@ class HookHandler:
224
224
  """Get git branch for the given directory with caching."""
225
225
  # Use current working directory if not specified
226
226
  if not working_dir:
227
- working_dir = os.getcwd()
227
+ working_dir = Path.cwd()
228
228
 
229
229
  # Check cache first (cache for 30 seconds)
230
230
  current_time = time.time()
@@ -240,7 +240,7 @@ class HookHandler:
240
240
  # Try to get git branch
241
241
  try:
242
242
  # Change to the working directory temporarily
243
- original_cwd = os.getcwd()
243
+ original_cwd = Path.cwd()
244
244
  os.chdir(working_dir)
245
245
 
246
246
  # Run git command to get current branch
@@ -313,7 +313,7 @@ class ClaudeHookHandler:
313
313
  """
314
314
  # Use current working directory if not specified
315
315
  if not working_dir:
316
- working_dir = os.getcwd()
316
+ working_dir = Path.cwd()
317
317
 
318
318
  # Check cache first (cache for 30 seconds)
319
319
  current_time = datetime.now(timezone.utc).timestamp()
@@ -329,7 +329,7 @@ class ClaudeHookHandler:
329
329
  # Try to get git branch
330
330
  try:
331
331
  # Change to the working directory temporarily
332
- original_cwd = os.getcwd()
332
+ original_cwd = Path.cwd()
333
333
  os.chdir(working_dir)
334
334
 
335
335
  # Run git command to get current branch
@@ -37,7 +37,7 @@ find_claude_mpm() {
37
37
  cmd_path=$(readlink -f "$cmd_path")
38
38
  fi
39
39
  # Extract the base directory (usually site-packages or venv)
40
- local base_dir=$(python3 -c "import claude_mpm; import os; print(os.path.dirname(os.path.dirname(claude_mpm.__file__)))" 2>/dev/null)
40
+ local base_dir=$(python3 -c "import claude_mpm; import os; print(Path(os.path.dirname(claude_mpm.__file__).parent))" 2>/dev/null)
41
41
  if [ -n "$base_dir" ]; then
42
42
  echo "$base_dir"
43
43
  return 0
@@ -71,7 +71,7 @@ try:
71
71
  import claude_mpm
72
72
  import os
73
73
  # Get the package directory
74
- pkg_dir = os.path.dirname(claude_mpm.__file__)
74
+ pkg_dir = Path(claude_mpm.__file__).parent
75
75
  # Check if we're in a development install (src directory)
76
76
  if 'src' in pkg_dir:
77
77
  # Go up to find the project root
@@ -81,10 +81,10 @@ try:
81
81
  project_root = os.sep.join(parts[:src_idx])
82
82
  print(project_root)
83
83
  else:
84
- print(os.path.dirname(os.path.dirname(pkg_dir)))
84
+ print(Path(os.path.dirname(pkg_dir).parent))
85
85
  else:
86
86
  # Installed package - just return the package location
87
- print(os.path.dirname(pkg_dir))
87
+ print(Path(pkg_dir).parent)
88
88
  except Exception:
89
89
  pass
90
90
  " 2>/dev/null)
@@ -99,7 +99,7 @@ except Exception:
99
99
  for path_dir in $PATH; do
100
100
  if [ -f "$path_dir/claude-mpm" ]; then
101
101
  # Found claude-mpm executable, try to find its package
102
- local pkg_dir=$(cd "$path_dir" && python3 -c "import claude_mpm; import os; print(os.path.dirname(os.path.dirname(claude_mpm.__file__)))" 2>/dev/null)
102
+ local pkg_dir=$(cd "$path_dir" && python3 -c "import claude_mpm; import os; print(Path(os.path.dirname(claude_mpm.__file__).parent))" 2>/dev/null)
103
103
  if [ -n "$pkg_dir" ]; then
104
104
  echo "$pkg_dir"
105
105
  return 0
@@ -339,8 +339,8 @@ main "$@"
339
339
 
340
340
  # Make sure it's executable
341
341
  if script_path.exists():
342
- st = os.stat(script_path)
343
- os.chmod(script_path, st.st_mode | stat.S_IEXEC)
342
+ st = Path(script_path).stat()
343
+ Path(script_path).chmod(st.st_mode | stat.S_IEXEC)
344
344
  self._hook_script_path = script_path
345
345
  return script_path
346
346
 
@@ -588,9 +588,9 @@ main "$@"
588
588
  issues.append(f"Invalid Claude settings JSON: {e}")
589
589
 
590
590
  # Check if claude-mpm is accessible
591
- try:
592
- import claude_mpm
593
- except ImportError:
591
+ import importlib.util
592
+
593
+ if importlib.util.find_spec("claude_mpm") is None:
594
594
  issues.append("claude-mpm package not found in Python environment")
595
595
 
596
596
  is_valid = len(issues) == 0
@@ -10,6 +10,7 @@ import os
10
10
  import re
11
11
  import sys
12
12
  from datetime import datetime, timezone
13
+ from pathlib import Path
13
14
  from typing import Any, Optional
14
15
 
15
16
  # Debug mode
@@ -64,8 +65,8 @@ class ResponseTrackingManager:
64
65
  if config_file:
65
66
  # Use specific config file with ConfigLoader
66
67
  pattern = ConfigPattern(
67
- filenames=[os.path.basename(config_file)],
68
- search_paths=[os.path.dirname(config_file)],
68
+ filenames=[Path(config_file).name],
69
+ search_paths=[Path(config_file).parent],
69
70
  env_prefix="CLAUDE_MPM_",
70
71
  )
71
72
  config = config_loader.load_config(
@@ -12,6 +12,7 @@ import subprocess
12
12
  import time
13
13
  from collections import deque
14
14
  from datetime import datetime, timezone
15
+ from pathlib import Path
15
16
  from typing import Optional
16
17
 
17
18
  # Import constants for configuration
@@ -176,7 +177,7 @@ class StateManagerService:
176
177
  """
177
178
  # Use current working directory if not specified
178
179
  if not working_dir:
179
- working_dir = os.getcwd()
180
+ working_dir = Path.cwd()
180
181
 
181
182
  # Check cache first (cache for 30 seconds)
182
183
  current_time = datetime.now(timezone.utc).timestamp()
@@ -192,7 +193,7 @@ class StateManagerService:
192
193
  # Try to get git branch
193
194
  try:
194
195
  # Change to the working directory temporarily
195
- original_cwd = os.getcwd()
196
+ original_cwd = Path.cwd()
196
197
  os.chdir(working_dir)
197
198
 
198
199
  # Run git command to get current branch
@@ -125,10 +125,13 @@ class ToolCallInterceptor:
125
125
  f"[{result.get('hook_name', 'Unknown')}] {result.get('error')}"
126
126
  )
127
127
 
128
- if result.get("modified") and result.get("data"):
128
+ if (
129
+ result.get("modified")
130
+ and result.get("data")
131
+ and "parameters" in result.get("data", {})
132
+ ):
129
133
  # Update parameters if modified
130
- if "parameters" in result.get("data", {}):
131
- modified_params = result["data"]["parameters"]
134
+ modified_params = result["data"]["parameters"]
132
135
 
133
136
  if result.get("metadata"):
134
137
  hook_metadata.update(result["metadata"])
@@ -531,6 +531,8 @@ class AgentSession:
531
531
 
532
532
  WHY: Enables analysis of historical sessions.
533
533
  """
534
- with open(filepath, encoding="utf-8") as f:
534
+ with Path(filepath).open(
535
+ encoding="utf-8",
536
+ ) as f:
535
537
  data = json.load(f)
536
538
  return cls.from_dict(data)
@@ -6,15 +6,13 @@ It handles proper Python path setup and error reporting to stderr.
6
6
  """
7
7
 
8
8
  import logging
9
- import os
10
9
  import sys
10
+ from pathlib import Path
11
11
 
12
12
  # Since we're now in src/claude_mpm/scripts/, we need to go up 3 levels to reach the project root
13
13
  # Then down into src to add it to the path
14
- project_root = os.path.abspath(
15
- os.path.join(os.path.dirname(__file__), "..", "..", "..")
16
- )
17
- sys.path.insert(0, os.path.join(project_root, "src"))
14
+ project_root = Path(Path(Path(__file__).parent.resolve().joinpath(), "..", "..", ".."))
15
+ sys.path.insert(0, Path(project_root) / "src")
18
16
 
19
17
 
20
18
  def setup_logging():
@@ -12,7 +12,7 @@ import json
12
12
  import re
13
13
  from datetime import datetime, timezone
14
14
  from pathlib import Path
15
- from typing import Any, Dict, List, Optional, Tuple
15
+ from typing import Any, ClassVar, Dict, List, Optional, Tuple
16
16
 
17
17
  from claude_mpm.core.exceptions import AgentDeploymentError
18
18
  from claude_mpm.core.logging_config import get_logger
@@ -22,13 +22,13 @@ class AgentBuilderService:
22
22
  """Service for building and managing agent configurations."""
23
23
 
24
24
  # Valid agent models
25
- VALID_MODELS = ["sonnet", "opus", "haiku"]
25
+ VALID_MODELS: ClassVar[list[str]] = ["sonnet", "opus", "haiku"]
26
26
 
27
27
  # Valid tool choices
28
- VALID_TOOL_CHOICES = ["auto", "required", "any", "none"]
28
+ VALID_TOOL_CHOICES: ClassVar[list[str]] = ["auto", "required", "any", "none"]
29
29
 
30
30
  # Agent categories
31
- AGENT_CATEGORIES = [
31
+ AGENT_CATEGORIES: ClassVar[list[str]] = [
32
32
  "engineering",
33
33
  "qa",
34
34
  "documentation",
@@ -63,13 +63,11 @@ class DeploymentTypeDetector:
63
63
  """
64
64
  # Check if we're in a project directory with .claude-mpm/agents
65
65
  project_agents_dir = working_directory / ".claude-mpm" / "agents"
66
- if project_agents_dir.exists():
67
- # Check if templates_dir points to project agents
68
- if templates_dir and templates_dir.exists():
69
- try:
70
- return project_agents_dir.resolve() == templates_dir.resolve()
71
- except Exception:
72
- pass
66
+ if project_agents_dir.exists() and templates_dir and templates_dir.exists():
67
+ try:
68
+ return project_agents_dir.resolve() == templates_dir.resolve()
69
+ except Exception:
70
+ pass
73
71
  return False
74
72
 
75
73
  @staticmethod
@@ -86,13 +84,11 @@ class DeploymentTypeDetector:
86
84
  True if deploying user custom agents, False otherwise
87
85
  """
88
86
  user_agents_dir = Path.home() / ".claude-mpm" / "agents"
89
- if user_agents_dir.exists():
90
- # Check if templates_dir points to user agents
91
- if templates_dir and templates_dir.exists():
92
- try:
93
- return user_agents_dir.resolve() == templates_dir.resolve()
94
- except Exception:
95
- pass
87
+ if user_agents_dir.exists() and templates_dir and templates_dir.exists():
88
+ try:
89
+ return user_agents_dir.resolve() == templates_dir.resolve()
90
+ except Exception:
91
+ pass
96
92
  return False
97
93
 
98
94
  @staticmethod
@@ -330,9 +330,12 @@ class LocalTemplateDeploymentService:
330
330
  for agent_id, template in templates.items():
331
331
  if agent_id in deployed:
332
332
  # Check if needs update
333
- if deployed[agent_id]["version"] != template.agent_version:
334
- if self.deploy_single_local_template(agent_id, force_rebuild=True):
335
- results["updated"].append(agent_id)
333
+ if deployed[agent_id][
334
+ "version"
335
+ ] != template.agent_version and self.deploy_single_local_template(
336
+ agent_id, force_rebuild=True
337
+ ):
338
+ results["updated"].append(agent_id)
336
339
  # New agent to deploy
337
340
  elif self.deploy_single_local_template(agent_id):
338
341
  results["added"].append(agent_id)
@@ -209,13 +209,15 @@ class MultiSourceAgentDeploymentService:
209
209
  f"User agent '{agent_name}' v{other_agent['version']} "
210
210
  f"overridden by higher {highest_version_agent['source']} version v{highest_version_agent['version']}"
211
211
  )
212
- elif version_comparison == 0:
212
+ elif (
213
+ version_comparison == 0
214
+ and other_agent["source"] != highest_version_agent["source"]
215
+ ):
213
216
  # Log info when versions are equal but different sources
214
- if other_agent["source"] != highest_version_agent["source"]:
215
- self.logger.info(
216
- f"Using {highest_version_agent['source']} source for '{agent_name}' "
217
- f"(same version v{highest_version_agent['version']} as {other_agent['source']} source)"
218
- )
217
+ self.logger.info(
218
+ f"Using {highest_version_agent['source']} source for '{agent_name}' "
219
+ f"(same version v{highest_version_agent['version']} as {other_agent['source']} source)"
220
+ )
219
221
 
220
222
  return selected_agents
221
223
 
@@ -850,11 +852,13 @@ class MultiSourceAgentDeploymentService:
850
852
  ):
851
853
  # In development mode, unknown agents are likely system agents being tested
852
854
  return "system"
853
- if deployment_context == DeploymentContext.PIPX_INSTALL:
854
- # In pipx mode, unknown agents could be system agents
855
- # Check if agent follows system naming patterns
856
- if agent_name.count("-") <= 2 and len(agent_name) <= 20:
857
- return "system"
855
+ if (
856
+ deployment_context == DeploymentContext.PIPX_INSTALL
857
+ and agent_name.count("-") <= 2
858
+ and len(agent_name) <= 20
859
+ ):
860
+ # In pipx mode, check if agent follows system naming patterns
861
+ return "system"
858
862
  except Exception:
859
863
  pass
860
864
 
@@ -70,12 +70,15 @@ class SystemInstructionsDeployer:
70
70
  target_file = claude_dir / target_name
71
71
 
72
72
  # Check if update needed
73
- if not force_rebuild and target_file.exists():
74
- # Compare modification times
75
- if target_file.stat().st_mtime >= source_path.stat().st_mtime:
76
- results["skipped"].append(target_name)
77
- self.logger.debug(f"Framework file {target_name} up to date")
78
- continue
73
+ if (
74
+ not force_rebuild
75
+ and target_file.exists()
76
+ and target_file.stat().st_mtime >= source_path.stat().st_mtime
77
+ ):
78
+ # File is up to date based on modification time
79
+ results["skipped"].append(target_name)
80
+ self.logger.debug(f"Framework file {target_name} up to date")
81
+ continue
79
82
 
80
83
  # Read and deploy framework file
81
84
  file_content = source_path.read_text()
@@ -21,7 +21,6 @@ multi-file implementation for better maintainability.
21
21
 
22
22
  import asyncio
23
23
  import json
24
- import os
25
24
  from dataclasses import dataclass, field
26
25
  from datetime import datetime, timezone
27
26
  from enum import Enum
@@ -113,7 +112,7 @@ class AgentProfileLoader(BaseService):
113
112
  super().__init__(name="agent_profile_loader", config=config)
114
113
 
115
114
  # Core configuration
116
- self.working_directory = Path(os.getcwd())
115
+ self.working_directory = Path(Path.cwd())
117
116
  self.framework_path = self._detect_framework_path()
118
117
  self.user_home = Path.home()
119
118
 
@@ -22,8 +22,7 @@ following the naming convention: {agent_id}_memories.md
22
22
  """
23
23
 
24
24
  import logging
25
- import os
26
- from typing import Any, Dict, List, Optional, Tuple
25
+ from typing import Any, ClassVar, Dict, List, Optional, Tuple
27
26
 
28
27
  from claude_mpm.core.config import Config
29
28
  from claude_mpm.core.interfaces import MemoryServiceInterface
@@ -54,7 +53,7 @@ class AgentMemoryManager(MemoryServiceInterface):
54
53
 
55
54
  # Default limits - will be overridden by configuration
56
55
  # Updated to support 20k tokens (~80KB) for enhanced memory capacity
57
- DEFAULT_MEMORY_LIMITS = {
56
+ DEFAULT_MEMORY_LIMITS: ClassVar[dict[str, int]] = {
58
57
  "max_file_size_kb": 80, # Increased from 8KB to 80KB (20k tokens)
59
58
  "max_items": 100, # Maximum total memory items
60
59
  "max_line_length": 120,
@@ -78,7 +77,7 @@ class AgentMemoryManager(MemoryServiceInterface):
78
77
  self.config = config or Config()
79
78
  self.project_root = get_path_manager().project_root
80
79
  # Use current working directory by default, not project root
81
- self.working_directory = working_directory or Path(os.getcwd())
80
+ self.working_directory = working_directory or Path(Path.cwd())
82
81
 
83
82
  # Use only project memory directory
84
83
  self.project_memories_dir = self.working_directory / ".claude-mpm" / "memories"
@@ -387,31 +386,32 @@ class AgentMemoryManager(MemoryServiceInterface):
387
386
  memory_items = data["Remember"]
388
387
 
389
388
  # Process memory items if found and not null
390
- if memory_items is not None and memory_items != "null":
391
- # Skip if explicitly null or empty list
392
- if isinstance(memory_items, list) and len(memory_items) > 0:
393
- # Filter out empty strings and None values
394
- valid_items = []
395
- for item in memory_items:
396
- if item and isinstance(item, str) and item.strip():
397
- valid_items.append(item.strip())
398
-
399
- # Only proceed if we have valid items
400
- if valid_items:
389
+ if (
390
+ memory_items is not None
391
+ and memory_items != "null"
392
+ and isinstance(memory_items, list)
393
+ and len(memory_items) > 0
394
+ ):
395
+ # Filter out empty strings and None values
396
+ valid_items = []
397
+ for item in memory_items:
398
+ if item and isinstance(item, str) and item.strip():
399
+ valid_items.append(item.strip())
400
+
401
+ # Only proceed if we have valid items
402
+ if valid_items:
403
+ self.logger.info(
404
+ f"Found {len(valid_items)} memory items for {agent_id}: {valid_items[:2]}..."
405
+ )
406
+ success = self._add_learnings_to_memory(
407
+ agent_id, valid_items
408
+ )
409
+ if success:
401
410
  self.logger.info(
402
- f"Found {len(valid_items)} memory items for {agent_id}: {valid_items[:2]}..."
403
- )
404
- success = self._add_learnings_to_memory(
405
- agent_id, valid_items
406
- )
407
- if success:
408
- self.logger.info(
409
- f"Successfully saved {len(valid_items)} memories for {agent_id} to project directory"
410
- )
411
- return True
412
- self.logger.error(
413
- f"Failed to save memories for {agent_id}"
411
+ f"Successfully saved {len(valid_items)} memories for {agent_id} to project directory"
414
412
  )
413
+ return True
414
+ self.logger.error(f"Failed to save memories for {agent_id}")
415
415
 
416
416
  except json.JSONDecodeError as je:
417
417
  # Not valid JSON, continue to next match
@@ -363,10 +363,15 @@ class MemoryContentManager:
363
363
 
364
364
  # Additional check: if one string contains the other (substring match)
365
365
  # This catches cases where one item is a more detailed version of another
366
- if len(str1_normalized) > 20 and len(str2_normalized) > 20:
367
- if str1_normalized in str2_normalized or str2_normalized in str1_normalized:
368
- # Boost similarity for substring matches
369
- similarity = max(similarity, 0.85)
366
+ if (
367
+ len(str1_normalized) > 20
368
+ and len(str2_normalized) > 20
369
+ and (
370
+ str1_normalized in str2_normalized or str2_normalized in str1_normalized
371
+ )
372
+ ):
373
+ # Boost similarity for substring matches
374
+ similarity = max(similarity, 0.85)
370
375
 
371
376
  return similarity
372
377
 
@@ -10,6 +10,8 @@ Now with optional async logging support for improved performance.
10
10
  Configuration via .claude-mpm/configuration.yaml.
11
11
  """
12
12
 
13
+ # Try to import async logger for performance optimization
14
+ import importlib.util
13
15
  import json
14
16
  import os
15
17
  from datetime import datetime, timezone
@@ -22,16 +24,11 @@ from claude_mpm.core.config import Config
22
24
  # Import centralized session manager
23
25
  from claude_mpm.services.session_manager import get_session_manager
24
26
 
25
- # Try to import async logger for performance optimization
26
- try:
27
- from claude_mpm.services.async_session_logger import (
28
- AsyncSessionLogger,
29
- get_async_logger,
30
- log_response_async,
31
- )
27
+ if importlib.util.find_spec("claude_mpm.services.async_session_logger"):
28
+ from claude_mpm.services.async_session_logger import get_async_logger
32
29
 
33
30
  ASYNC_AVAILABLE = True
34
- except ImportError:
31
+ else:
35
32
  ASYNC_AVAILABLE = False
36
33
 
37
34
  from claude_mpm.core.logging_utils import get_logger
@@ -142,7 +142,7 @@ class MemoryCRUDService(IMemoryCRUDService):
142
142
  config_loader = ConfigLoader()
143
143
  config = config_loader.load_main_config()
144
144
  # Use CLAUDE_MPM_USER_PWD if available
145
- user_pwd = os.environ.get("CLAUDE_MPM_USER_PWD", os.getcwd())
145
+ user_pwd = os.environ.get("CLAUDE_MPM_USER_PWD", Path.cwd())
146
146
  current_dir = Path(user_pwd)
147
147
  self._memory_manager = AgentMemoryManager(config, current_dir)
148
148
  return self._memory_manager
@@ -90,7 +90,7 @@ class MemoryOutputFormatter(IMemoryOutputFormatter):
90
90
  "success": "✅" if not quiet else "[OK]",
91
91
  "error": "❌" if not quiet else "[ERROR]",
92
92
  "warning": "⚠️" if not quiet else "[WARN]",
93
- "info": "ℹ️" if not quiet else "[INFO]",
93
+ "info": "[INFO]️" if not quiet else "[INFO]",
94
94
  "memory": "🧠" if not quiet else "[MEMORY]",
95
95
  "file": "📁" if not quiet else "[FILE]",
96
96
  "agent": "🤖" if not quiet else "[AGENT]",