claude-mpm 4.6.0__py3-none-any.whl → 4.7.0__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 +2 -3
  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.0.dist-info → claude_mpm-4.7.0.dist-info}/METADATA +1 -1
  107. {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/RECORD +111 -110
  108. {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/WHEEL +0 -0
  109. {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/licenses/LICENSE +0 -0
  111. {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,6 @@ DESIGN DECISIONS:
12
12
  - Compatible with UnifiedMonitorServer architecture
13
13
  """
14
14
 
15
- import os
16
15
  from pathlib import Path
17
16
  from typing import Any, Dict, Optional
18
17
 
@@ -56,7 +55,7 @@ class FileHandler:
56
55
 
57
56
  try:
58
57
  file_path = data.get("file_path")
59
- working_dir = data.get("working_dir", os.getcwd())
58
+ working_dir = data.get("working_dir", Path.cwd())
60
59
  max_size = data.get("max_size", 1024 * 1024) # 1MB default limit
61
60
 
62
61
  if not file_path:
@@ -126,7 +125,7 @@ class FileHandler:
126
125
  """
127
126
  try:
128
127
  if working_dir is None:
129
- working_dir = os.getcwd()
128
+ working_dir = Path.cwd()
130
129
 
131
130
  # Resolve absolute path based on working directory
132
131
  file_path_obj = Path(file_path)
@@ -192,7 +191,9 @@ class FileHandler:
192
191
 
193
192
  # Read file content
194
193
  try:
195
- with open(real_path, encoding="utf-8") as f:
194
+ with Path(real_path).open(
195
+ encoding="utf-8",
196
+ ) as f:
196
197
  content = f.read()
197
198
 
198
199
  # Get file extension for syntax highlighting hint
@@ -121,7 +121,7 @@ class DaemonLifecycle:
121
121
  sys.stderr.flush()
122
122
 
123
123
  # Redirect stdin to /dev/null
124
- with open("/dev/null") as null_in:
124
+ with Path("/dev/null").open() as null_in:
125
125
  os.dup2(null_in.fileno(), sys.stdin.fileno())
126
126
 
127
127
  # Redirect stdout and stderr
@@ -368,25 +368,24 @@ class UnifiedMonitorServer:
368
368
  async def api_file_handler(request):
369
369
  """Handle file content requests."""
370
370
  import json
371
- import os
372
371
 
373
372
  try:
374
373
  data = await request.json()
375
374
  file_path = data.get("path", "")
376
375
 
377
376
  # Security check: ensure path is absolute and exists
378
- if not file_path or not os.path.isabs(file_path):
377
+ if not file_path or not Path(file_path).is_absolute():
379
378
  return web.json_response(
380
379
  {"success": False, "error": "Invalid file path"}, status=400
381
380
  )
382
381
 
383
382
  # Check if file exists and is readable
384
- if not os.path.exists(file_path):
383
+ if not Path(file_path).exists():
385
384
  return web.json_response(
386
385
  {"success": False, "error": "File not found"}, status=404
387
386
  )
388
387
 
389
- if not os.path.isfile(file_path):
388
+ if not Path(file_path).is_file():
390
389
  return web.json_response(
391
390
  {"success": False, "error": "Path is not a file"},
392
391
  status=400,
@@ -394,7 +393,7 @@ class UnifiedMonitorServer:
394
393
 
395
394
  # Read file content (with size limit for safety)
396
395
  max_size = 10 * 1024 * 1024 # 10MB limit
397
- file_size = os.path.getsize(file_path)
396
+ file_size = Path(file_path).stat().st_size
398
397
 
399
398
  if file_size > max_size:
400
399
  return web.json_response(
@@ -406,7 +405,9 @@ class UnifiedMonitorServer:
406
405
  )
407
406
 
408
407
  try:
409
- with open(file_path, encoding="utf-8") as f:
408
+ with Path(file_path).open(
409
+ encoding="utf-8",
410
+ ) as f:
410
411
  content = f.read()
411
412
  lines = content.count("\n") + 1
412
413
  except UnicodeDecodeError:
@@ -417,7 +418,7 @@ class UnifiedMonitorServer:
417
418
  )
418
419
 
419
420
  # Get file extension for type detection
420
- file_ext = os.path.splitext(file_path)[1].lstrip(".")
421
+ file_ext = Path(file_path).suffix.lstrip(".")
421
422
 
422
423
  return web.json_response(
423
424
  {
@@ -475,11 +476,10 @@ class UnifiedMonitorServer:
475
476
  # Configuration endpoint for dashboard initialization
476
477
  async def config_handler(request):
477
478
  """Return configuration for dashboard initialization."""
478
- import os
479
479
  import subprocess
480
480
 
481
481
  config = {
482
- "workingDirectory": os.getcwd(),
482
+ "workingDirectory": Path.cwd(),
483
483
  "gitBranch": "Unknown",
484
484
  "serverTime": datetime.now(timezone.utc).isoformat() + "Z",
485
485
  "service": "unified-monitor",
@@ -492,7 +492,7 @@ class UnifiedMonitorServer:
492
492
  capture_output=True,
493
493
  text=True,
494
494
  timeout=2,
495
- cwd=os.getcwd(),
495
+ cwd=Path.cwd(),
496
496
  check=False,
497
497
  )
498
498
  if result.returncode == 0 and result.stdout.strip():
@@ -506,7 +506,7 @@ class UnifiedMonitorServer:
506
506
  async def working_directory_handler(request):
507
507
  """Return the current working directory."""
508
508
  return web.json_response(
509
- {"working_directory": os.getcwd(), "success": True}
509
+ {"working_directory": Path.cwd(), "success": True}
510
510
  )
511
511
 
512
512
  # Monitor page routes
@@ -517,7 +517,9 @@ class UnifiedMonitorServer:
517
517
  file_path = static_dir / f"{page_name}.html"
518
518
 
519
519
  if file_path.exists() and file_path.is_file():
520
- with open(file_path, encoding="utf-8") as f:
520
+ with Path(file_path).open(
521
+ encoding="utf-8",
522
+ ) as f:
521
523
  content = f.read()
522
524
  return web.Response(text=content, content_type="text/html")
523
525
  return web.Response(text="Page not found", status=404)
@@ -15,7 +15,7 @@ import logging
15
15
  import re
16
16
  from dataclasses import asdict, dataclass
17
17
  from pathlib import Path
18
- from typing import Dict, List
18
+ from typing import ClassVar, Dict, List
19
19
 
20
20
 
21
21
  @dataclass
@@ -44,7 +44,7 @@ class ArchitectureAnalyzerService:
44
44
  """
45
45
 
46
46
  # Common architectural directories
47
- ARCHITECTURE_INDICATORS = {
47
+ ARCHITECTURE_INDICATORS: ClassVar[dict] = {
48
48
  "mvc": ["models", "views", "controllers"],
49
49
  "mvvm": ["models", "views", "viewmodels"],
50
50
  "layered": ["presentation", "business", "data", "domain"],
@@ -58,7 +58,7 @@ class ArchitectureAnalyzerService:
58
58
  }
59
59
 
60
60
  # Entry point patterns by language/framework
61
- ENTRY_POINT_PATTERNS = {
61
+ ENTRY_POINT_PATTERNS: ClassVar[dict] = {
62
62
  "python": [
63
63
  "main.py",
64
64
  "app.py",
@@ -77,7 +77,7 @@ class ArchitectureAnalyzerService:
77
77
  }
78
78
 
79
79
  # API pattern indicators
80
- API_INDICATORS = {
80
+ API_INDICATORS: ClassVar[dict] = {
81
81
  "rest": ["routes", "endpoints", "resources", "api/v", "/api/"],
82
82
  "graphql": ["schema.graphql", "resolvers", "typeDefs", "graphql"],
83
83
  "grpc": [".proto", "grpc", "protobuf", "rpc"],
@@ -86,7 +86,7 @@ class ArchitectureAnalyzerService:
86
86
  }
87
87
 
88
88
  # Configuration file patterns
89
- CONFIG_PATTERNS = {
89
+ CONFIG_PATTERNS: ClassVar[dict] = {
90
90
  "yaml": [".yaml", ".yml"],
91
91
  "json": [".json", "config.json", "settings.json"],
92
92
  "toml": [".toml", "pyproject.toml", "Cargo.toml"],
@@ -15,7 +15,7 @@ import logging
15
15
  from collections import Counter
16
16
  from dataclasses import asdict, dataclass
17
17
  from pathlib import Path
18
- from typing import Dict, List, Tuple
18
+ from typing import ClassVar, Dict, List, Tuple
19
19
 
20
20
 
21
21
  @dataclass
@@ -68,7 +68,7 @@ class MetricsCollectorService:
68
68
  """
69
69
 
70
70
  # File extensions to analyze
71
- CODE_EXTENSIONS = {
71
+ CODE_EXTENSIONS: ClassVar[set] = {
72
72
  ".py",
73
73
  ".js",
74
74
  ".ts",
@@ -90,7 +90,7 @@ class MetricsCollectorService:
90
90
  }
91
91
 
92
92
  # Test file patterns
93
- TEST_PATTERNS = [
93
+ TEST_PATTERNS: ClassVar[list] = [
94
94
  "test_",
95
95
  "_test.",
96
96
  ".test.",
@@ -103,7 +103,7 @@ class MetricsCollectorService:
103
103
  ]
104
104
 
105
105
  # Directories to exclude from analysis
106
- EXCLUDE_DIRS = {
106
+ EXCLUDE_DIRS: ClassVar[set] = {
107
107
  ".git",
108
108
  "node_modules",
109
109
  "vendor",
@@ -17,7 +17,7 @@ Created: 2025-01-26
17
17
  """
18
18
 
19
19
  from pathlib import Path
20
- from typing import Dict, List, Optional, Tuple
20
+ from typing import ClassVar, Dict, List, Optional, Tuple
21
21
 
22
22
  from rich.console import Console
23
23
 
@@ -31,7 +31,7 @@ class ProjectOrganizer:
31
31
  """Manages project directory structure and organization."""
32
32
 
33
33
  # Standard directory structure for Claude MPM projects
34
- STANDARD_DIRECTORIES = {
34
+ STANDARD_DIRECTORIES: ClassVar[dict] = {
35
35
  "tmp": "Temporary files, test outputs, and experiments",
36
36
  "scripts": "Project scripts and automation tools",
37
37
  "docs": "Project documentation",
@@ -44,7 +44,7 @@ class ProjectOrganizer:
44
44
  }
45
45
 
46
46
  # Comprehensive gitignore patterns for Claude MPM projects
47
- GITIGNORE_DIRS = {
47
+ GITIGNORE_DIRS: ClassVar[dict] = {
48
48
  # Temporary and cache directories
49
49
  "tmp/",
50
50
  "temp/",
@@ -160,7 +160,7 @@ class ProjectOrganizer:
160
160
  }
161
161
 
162
162
  # Project type specific structures
163
- PROJECT_STRUCTURES = {
163
+ PROJECT_STRUCTURES: ClassVar[dict] = {
164
164
  "web": ["public", "src/components", "src/pages", "src/styles"],
165
165
  "api": ["src/routes", "src/models", "src/middleware", "src/services"],
166
166
  "cli": ["src/commands", "src/utils", "src/config"],
@@ -124,7 +124,9 @@ class ProjectRegistry:
124
124
  # Search all registry files
125
125
  for registry_file in self.registry_dir.glob("*.yaml"):
126
126
  try:
127
- with open(registry_file, encoding="utf-8") as f:
127
+ with Path(registry_file).open(
128
+ encoding="utf-8",
129
+ ) as f:
128
130
  data = yaml.safe_load(f) or {}
129
131
 
130
132
  # Check if project_path matches
@@ -517,7 +519,9 @@ class ProjectRegistry:
517
519
  try:
518
520
  for registry_file in self.registry_dir.glob("*.yaml"):
519
521
  try:
520
- with open(registry_file, encoding="utf-8") as f:
522
+ with Path(registry_file).open(
523
+ encoding="utf-8",
524
+ ) as f:
521
525
  data = yaml.safe_load(f) or {}
522
526
  projects.append(data)
523
527
  except Exception as e:
@@ -553,7 +557,9 @@ class ProjectRegistry:
553
557
  try:
554
558
  for registry_file in self.registry_dir.glob("*.yaml"):
555
559
  try:
556
- with open(registry_file, encoding="utf-8") as f:
560
+ with Path(registry_file).open(
561
+ encoding="utf-8",
562
+ ) as f:
557
563
  data = yaml.safe_load(f) or {}
558
564
 
559
565
  # Check last accessed time
@@ -99,9 +99,8 @@ class ConfigServiceBase(LoggerMixin, ABC):
99
99
  raise ValueError(f"Required configuration value missing: {full_key}")
100
100
 
101
101
  # Type validation
102
- if config_type is not None and value is not None:
103
- if not isinstance(value, config_type):
104
- try:
102
+ if config_type is not None and value is not None and not isinstance(value, config_type):
103
+ try:
105
104
  # Try to convert
106
105
  if config_type == bool and isinstance(value, str):
107
106
  value = value.lower() in ("true", "1", "yes", "on")
@@ -5,7 +5,6 @@ safely with security checks. Separating file operations improves security
5
5
  auditing and makes it easier to add file-related features.
6
6
  """
7
7
 
8
- import os
9
8
  from pathlib import Path
10
9
  from typing import Any, Dict, Optional
11
10
 
@@ -44,7 +43,7 @@ class FileEventHandler(BaseEventHandler):
44
43
  )
45
44
  try:
46
45
  file_path = data.get("file_path")
47
- working_dir = data.get("working_dir", os.getcwd())
46
+ working_dir = data.get("working_dir", Path.cwd())
48
47
  max_size = data.get("max_size", 1024 * 1024) # 1MB default limit
49
48
 
50
49
  if not file_path:
@@ -125,7 +124,7 @@ class FileEventHandler(BaseEventHandler):
125
124
  """
126
125
  try:
127
126
  if working_dir is None:
128
- working_dir = os.getcwd()
127
+ working_dir = Path.cwd()
129
128
 
130
129
  # Resolve absolute path based on working directory
131
130
  file_path_obj = Path(file_path)
@@ -191,7 +190,9 @@ class FileEventHandler(BaseEventHandler):
191
190
 
192
191
  # Read file content
193
192
  try:
194
- with open(real_path, encoding="utf-8") as f:
193
+ with Path(real_path).open(
194
+ encoding="utf-8",
195
+ ) as f:
195
196
  content = f.read()
196
197
 
197
198
  # Get file extension for syntax highlighting hint
@@ -106,7 +106,7 @@ class GitEventHandler(BaseEventHandler):
106
106
  """
107
107
  try:
108
108
  file_path = data.get("file_path")
109
- working_dir = data.get("working_dir", os.getcwd())
109
+ working_dir = data.get("working_dir", Path.cwd())
110
110
 
111
111
  if not file_path:
112
112
  await self.emit_to_client(
@@ -162,7 +162,7 @@ class GitEventHandler(BaseEventHandler):
162
162
  """
163
163
  try:
164
164
  file_path = data.get("file_path")
165
- working_dir = data.get("working_dir", os.getcwd())
165
+ working_dir = data.get("working_dir", Path.cwd())
166
166
 
167
167
  # Debug: check_git_status called
168
168
 
@@ -287,7 +287,7 @@ class GitEventHandler(BaseEventHandler):
287
287
  """
288
288
  try:
289
289
  file_path = data.get("file_path")
290
- working_dir = data.get("working_dir", os.getcwd())
290
+ working_dir = data.get("working_dir", Path.cwd())
291
291
 
292
292
  # Debug: git_add_file called
293
293
 
@@ -389,7 +389,7 @@ class GitEventHandler(BaseEventHandler):
389
389
  if working_dir in invalid_states or (
390
390
  isinstance(working_dir, str) and working_dir.strip() == ""
391
391
  ):
392
- working_dir = os.getcwd()
392
+ working_dir = Path.cwd()
393
393
  self.logger.info(
394
394
  f"[{operation}] working_dir was invalid ({original_working_dir!r}), using cwd: {working_dir}"
395
395
  )
@@ -404,7 +404,7 @@ class GitEventHandler(BaseEventHandler):
404
404
  self.logger.warning(
405
405
  f"[{operation}] working_dir contains null bytes, using cwd instead"
406
406
  )
407
- working_dir = os.getcwd()
407
+ working_dir = Path.cwd()
408
408
 
409
409
  return working_dir
410
410
 
@@ -640,7 +640,7 @@ class GitEventHandler(BaseEventHandler):
640
640
 
641
641
  # Handle case where working_dir is None, empty string, or 'Unknown'
642
642
  if not working_dir or working_dir == "Unknown" or working_dir.strip() == "":
643
- working_dir = os.getcwd()
643
+ working_dir = Path.cwd()
644
644
  # Debug: working_dir was invalid, using cwd
645
645
  else:
646
646
  # Debug: Using provided working_dir
@@ -648,7 +648,7 @@ class GitEventHandler(BaseEventHandler):
648
648
 
649
649
  # For read-only git operations, we can work from any directory
650
650
  # by passing the -C flag to git commands instead of changing directories
651
- original_cwd = os.getcwd()
651
+ original_cwd = Path.cwd()
652
652
  try:
653
653
  # We'll use git -C <working_dir> for all commands instead of chdir
654
654
 
@@ -396,11 +396,10 @@ class SocketIOServerCore:
396
396
  # Add working directory endpoint
397
397
  async def working_directory_handler(request):
398
398
  """Handle GET /api/working-directory to provide current working directory."""
399
- import os
400
399
  from pathlib import Path
401
400
 
402
401
  try:
403
- working_dir = os.getcwd()
402
+ working_dir = Path.cwd()
404
403
  home_dir = str(Path.home())
405
404
 
406
405
  return web.json_response(
@@ -430,27 +429,26 @@ class SocketIOServerCore:
430
429
  # Add file reading endpoint for source viewer
431
430
  async def file_read_handler(request):
432
431
  """Handle GET /api/file/read for reading source files."""
433
- import os
434
432
 
435
433
  file_path = request.query.get("path", "")
436
434
 
437
435
  if not file_path:
438
436
  return web.json_response({"error": "No path provided"}, status=400)
439
437
 
440
- abs_path = os.path.abspath(os.path.expanduser(file_path))
438
+ abs_path = Path(Path(file_path).resolve().expanduser())
441
439
 
442
440
  # Security check - ensure file is within the project
443
441
  try:
444
- project_root = os.getcwd()
442
+ project_root = Path.cwd()
445
443
  if not abs_path.startswith(project_root):
446
444
  return web.json_response({"error": "Access denied"}, status=403)
447
445
  except Exception:
448
446
  pass
449
447
 
450
- if not os.path.exists(abs_path):
448
+ if not Path(abs_path).exists():
451
449
  return web.json_response({"error": "File not found"}, status=404)
452
450
 
453
- if not os.path.isfile(abs_path):
451
+ if not Path(abs_path).is_file():
454
452
  return web.json_response({"error": "Not a file"}, status=400)
455
453
 
456
454
  try:
@@ -460,7 +458,9 @@ class SocketIOServerCore:
460
458
 
461
459
  for encoding in encodings:
462
460
  try:
463
- with open(abs_path, encoding=encoding) as f:
461
+ with Path(abs_path).open(
462
+ encoding=encoding,
463
+ ) as f:
464
464
  content = f.read()
465
465
  break
466
466
  except UnicodeDecodeError:
@@ -474,10 +474,10 @@ class SocketIOServerCore:
474
474
  return web.json_response(
475
475
  {
476
476
  "path": abs_path,
477
- "name": os.path.basename(abs_path),
477
+ "name": Path(abs_path).name,
478
478
  "content": content,
479
479
  "lines": len(content.splitlines()),
480
- "size": os.path.getsize(abs_path),
480
+ "size": Path(abs_path).stat().st_size,
481
481
  }
482
482
  )
483
483
 
@@ -268,16 +268,11 @@ class SubprocessLauncherService(BaseService, SubprocessLauncherInterface):
268
268
  Returns:
269
269
  True if subprocess mode with PTY is available
270
270
  """
271
- try:
272
- # Check if we can import required modules
273
- import pty
274
- import select
275
- import termios
276
- import tty
277
-
278
- return True
279
- except ImportError:
280
- return False
271
+ import importlib.util
272
+
273
+ # Check if we can import required modules
274
+ required = ["pty", "select", "termios", "tty"]
275
+ return all(importlib.util.find_spec(mod) is not None for mod in required)
281
276
 
282
277
  def create_subprocess_command(
283
278
  self, base_cmd: List[str], additional_args: Optional[List[str]] = None
@@ -278,7 +278,7 @@ class TicketFormatterService:
278
278
  Returns:
279
279
  Formatted info message
280
280
  """
281
- return f"ℹ️ {info}"
281
+ return f"[INFO]️ {info}"
282
282
 
283
283
  def format_warning(self, warning: str) -> str:
284
284
  """
@@ -11,17 +11,17 @@ DESIGN DECISIONS:
11
11
  - Provides sanitization for user inputs
12
12
  """
13
13
 
14
- from typing import Any, Dict, List, Optional, Tuple
14
+ from typing import Any, ClassVar, Dict, List, Optional, Tuple
15
15
 
16
16
 
17
17
  class TicketValidationService:
18
18
  """Service for validating ticket inputs."""
19
19
 
20
20
  # Valid ticket types
21
- VALID_TYPES = ["task", "issue", "epic", "bug", "feature", "story"]
21
+ VALID_TYPES: ClassVar[list] = ["task", "issue", "epic", "bug", "feature", "story"]
22
22
 
23
23
  # Valid ticket statuses
24
- VALID_STATUSES = [
24
+ VALID_STATUSES: ClassVar[list] = [
25
25
  "open",
26
26
  "in_progress",
27
27
  "ready",
@@ -34,10 +34,10 @@ class TicketValidationService:
34
34
  ]
35
35
 
36
36
  # Valid priorities
37
- VALID_PRIORITIES = ["low", "medium", "high", "critical"]
37
+ VALID_PRIORITIES: ClassVar[list] = ["low", "medium", "high", "critical"]
38
38
 
39
39
  # Valid workflow states
40
- VALID_WORKFLOW_STATES = [
40
+ VALID_WORKFLOW_STATES: ClassVar[list] = [
41
41
  "todo",
42
42
  "in_progress",
43
43
  "ready",
@@ -12,7 +12,7 @@ Created: 2025-01-26
12
12
  import json
13
13
  import re
14
14
  from pathlib import Path
15
- from typing import Any, Dict, List, Optional
15
+ from typing import Any, ClassVar, Dict, List, Optional
16
16
 
17
17
  from claude_mpm.core.logging_utils import get_logger
18
18
 
@@ -39,7 +39,7 @@ class DependencyAnalyzerStrategy(AnalyzerStrategy):
39
39
  """
40
40
 
41
41
  # Package manager configurations
42
- PACKAGE_MANAGERS = {
42
+ PACKAGE_MANAGERS: ClassVar[dict] = {
43
43
  "package.json": "npm",
44
44
  "yarn.lock": "yarn",
45
45
  "pnpm-lock.yaml": "pnpm",
@@ -59,7 +59,7 @@ class DependencyAnalyzerStrategy(AnalyzerStrategy):
59
59
  }
60
60
 
61
61
  # Database-related dependencies
62
- DATABASE_PACKAGES = {
62
+ DATABASE_PACKAGES: ClassVar[dict] = {
63
63
  "postgresql": ["psycopg2", "pg", "postgres", "postgresql", "node-postgres"],
64
64
  "mysql": ["mysql", "mysql2", "mysqlclient", "mysql-connector"],
65
65
  "sqlite": ["sqlite3", "better-sqlite3"],
@@ -71,7 +71,7 @@ class DependencyAnalyzerStrategy(AnalyzerStrategy):
71
71
  }
72
72
 
73
73
  # Testing framework packages
74
- TESTING_PACKAGES = {
74
+ TESTING_PACKAGES: ClassVar[dict] = {
75
75
  "python": ["pytest", "unittest", "nose", "nose2", "tox", "coverage"],
76
76
  "javascript": [
77
77
  "jest",
@@ -89,7 +89,7 @@ class DependencyAnalyzerStrategy(AnalyzerStrategy):
89
89
  }
90
90
 
91
91
  # Web framework packages
92
- FRAMEWORK_PACKAGES = {
92
+ FRAMEWORK_PACKAGES: ClassVar[dict] = {
93
93
  "python": ["django", "flask", "fastapi", "pyramid", "tornado", "aiohttp"],
94
94
  "javascript": [
95
95
  "express",
@@ -12,7 +12,7 @@ Created: 2025-01-26
12
12
  import ast
13
13
  import re
14
14
  from pathlib import Path
15
- from typing import Any, Dict, List, Optional
15
+ from typing import Any, ClassVar, Dict, List, Optional
16
16
 
17
17
  from claude_mpm.core.logging_utils import get_logger
18
18
 
@@ -39,7 +39,7 @@ class PerformanceAnalyzerStrategy(AnalyzerStrategy):
39
39
  """
40
40
 
41
41
  # Performance anti-patterns
42
- PERFORMANCE_PATTERNS = {
42
+ PERFORMANCE_PATTERNS: ClassVar[dict] = {
43
43
  "n_plus_one_query": {
44
44
  "patterns": [
45
45
  r"for .* in .*:\s*\n.*\.(get|filter|select|find)",
@@ -89,7 +89,7 @@ class PerformanceAnalyzerStrategy(AnalyzerStrategy):
89
89
  }
90
90
 
91
91
  # Algorithm complexity indicators
92
- COMPLEXITY_INDICATORS = {
92
+ COMPLEXITY_INDICATORS: ClassVar[dict] = {
93
93
  "quadratic": ["nested_loops", "bubble_sort", "selection_sort"],
94
94
  "exponential": ["recursive_fibonacci", "recursive_factorial"],
95
95
  "linear": ["single_loop", "map", "filter"],
@@ -98,7 +98,7 @@ class PerformanceAnalyzerStrategy(AnalyzerStrategy):
98
98
  }
99
99
 
100
100
  # Memory usage patterns
101
- MEMORY_PATTERNS = {
101
+ MEMORY_PATTERNS: ClassVar[dict] = {
102
102
  "memory_leak": {
103
103
  "patterns": [
104
104
  r"global\s+\w+\s*=",
@@ -12,7 +12,7 @@ Created: 2025-01-26
12
12
  import ast
13
13
  import re
14
14
  from pathlib import Path
15
- from typing import Any, Dict, List, Optional
15
+ from typing import Any, ClassVar, Dict, List, Optional
16
16
 
17
17
  from claude_mpm.core.logging_utils import get_logger
18
18
 
@@ -39,7 +39,7 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
39
39
  """
40
40
 
41
41
  # Common security vulnerability patterns
42
- VULNERABILITY_PATTERNS = {
42
+ VULNERABILITY_PATTERNS: ClassVar[dict] = {
43
43
  "sql_injection": {
44
44
  "patterns": [
45
45
  r'(execute|query)\s*\(\s*["\'].*%[s|d].*["\'].*%',
@@ -97,7 +97,7 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
97
97
  }
98
98
 
99
99
  # Insecure configuration patterns
100
- CONFIG_ISSUES = {
100
+ CONFIG_ISSUES: ClassVar[dict] = {
101
101
  "debug_enabled": {
102
102
  "patterns": [
103
103
  r"DEBUG\s*=\s*True",
@@ -128,7 +128,7 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
128
128
  }
129
129
 
130
130
  # Security headers to check
131
- SECURITY_HEADERS = [
131
+ SECURITY_HEADERS: ClassVar[list] = [
132
132
  "Content-Security-Policy",
133
133
  "X-Content-Type-Options",
134
134
  "X-Frame-Options",