claude-mpm 3.1.1__py3-none-any.whl → 3.1.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.
Files changed (47) hide show
  1. claude_mpm/__main__.py +27 -3
  2. claude_mpm/agents/INSTRUCTIONS.md +17 -2
  3. claude_mpm/agents/templates/test-integration-agent.md +34 -0
  4. claude_mpm/cli/README.md +109 -0
  5. claude_mpm/cli/__init__.py +172 -0
  6. claude_mpm/cli/commands/__init__.py +20 -0
  7. claude_mpm/cli/commands/agents.py +202 -0
  8. claude_mpm/cli/commands/info.py +94 -0
  9. claude_mpm/cli/commands/run.py +95 -0
  10. claude_mpm/cli/commands/tickets.py +70 -0
  11. claude_mpm/cli/commands/ui.py +79 -0
  12. claude_mpm/cli/parser.py +337 -0
  13. claude_mpm/cli/utils.py +190 -0
  14. claude_mpm/cli_enhancements.py +19 -0
  15. claude_mpm/core/agent_registry.py +4 -4
  16. claude_mpm/core/factories.py +1 -1
  17. claude_mpm/core/service_registry.py +1 -1
  18. claude_mpm/core/simple_runner.py +17 -27
  19. claude_mpm/hooks/claude_hooks/hook_handler.py +53 -4
  20. claude_mpm/models/__init__.py +106 -0
  21. claude_mpm/models/agent_definition.py +196 -0
  22. claude_mpm/models/common.py +41 -0
  23. claude_mpm/models/lifecycle.py +97 -0
  24. claude_mpm/models/modification.py +126 -0
  25. claude_mpm/models/persistence.py +57 -0
  26. claude_mpm/models/registry.py +91 -0
  27. claude_mpm/security/__init__.py +8 -0
  28. claude_mpm/security/bash_validator.py +393 -0
  29. claude_mpm/services/agent_lifecycle_manager.py +206 -94
  30. claude_mpm/services/agent_modification_tracker.py +27 -100
  31. claude_mpm/services/agent_persistence_service.py +74 -0
  32. claude_mpm/services/agent_registry.py +43 -82
  33. claude_mpm/services/agent_versioning.py +37 -0
  34. claude_mpm/services/{ticketing_service_original.py → legacy_ticketing_service.py} +16 -9
  35. claude_mpm/services/ticket_manager.py +5 -4
  36. claude_mpm/services/{ticket_manager_di.py → ticket_manager_dependency_injection.py} +39 -12
  37. claude_mpm/services/version_control/semantic_versioning.py +10 -9
  38. claude_mpm/utils/path_operations.py +20 -0
  39. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/METADATA +9 -1
  40. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/RECORD +45 -25
  41. claude_mpm/cli_main.py +0 -13
  42. claude_mpm/utils/import_migration_example.py +0 -80
  43. /claude_mpm/{cli.py → cli_old.py} +0 -0
  44. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/WHEEL +0 -0
  45. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/entry_points.txt +0 -0
  46. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/licenses/LICENSE +0 -0
  47. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,190 @@
1
+ """
2
+ Utility functions for the CLI.
3
+
4
+ WHY: This module contains shared utility functions used across different CLI commands.
5
+ Centralizing these functions reduces code duplication and provides a single place
6
+ for common CLI operations.
7
+ """
8
+
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ from claude_mpm.utils.imports import safe_import
14
+
15
+ # Import logger using safe_import pattern
16
+ get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
17
+
18
+
19
+ def get_user_input(input_arg: Optional[str], logger) -> str:
20
+ """
21
+ Get user input based on command line arguments.
22
+
23
+ WHY: This function handles the three ways users can provide input:
24
+ 1. Direct text via -i/--input
25
+ 2. File path via -i/--input
26
+ 3. stdin (for piping)
27
+
28
+ DESIGN DECISION: We check if the input is a file path first, then fall back
29
+ to treating it as direct text. This allows maximum flexibility.
30
+
31
+ Args:
32
+ input_arg: The value of the -i/--input argument
33
+ logger: Logger instance for output
34
+
35
+ Returns:
36
+ The user input as a string
37
+ """
38
+ if input_arg:
39
+ # Check if it's a file path
40
+ input_path = Path(input_arg)
41
+ if input_path.exists():
42
+ logger.info(f"Reading input from file: {input_path}")
43
+ return input_path.read_text()
44
+ else:
45
+ logger.info("Using command line input")
46
+ return input_arg
47
+ else:
48
+ # Read from stdin
49
+ logger.info("Reading input from stdin")
50
+ return sys.stdin.read()
51
+
52
+
53
+ def get_agent_versions_display() -> Optional[str]:
54
+ """
55
+ Get formatted agent versions display as a string.
56
+
57
+ WHY: This function provides a single source of truth for agent version
58
+ information that can be displayed both at startup and on-demand via the
59
+ /mpm agents command. This ensures consistency in how agent versions are
60
+ presented to users.
61
+
62
+ Returns:
63
+ Formatted string containing agent version information, or None if failed
64
+ """
65
+ # Import AgentDeploymentService using safe_import pattern
66
+ AgentDeploymentService = safe_import(
67
+ 'claude_mpm.services.agent_deployment',
68
+ None,
69
+ ['AgentDeploymentService']
70
+ )
71
+
72
+ if not AgentDeploymentService:
73
+ return None
74
+
75
+ try:
76
+ deployment_service = AgentDeploymentService()
77
+
78
+ # Get deployed agents
79
+ verification = deployment_service.verify_deployment()
80
+ if not verification.get("agents_found"):
81
+ return None
82
+
83
+ output_lines = []
84
+ output_lines.append("\nDeployed Agent Versions:")
85
+ output_lines.append("-" * 40)
86
+
87
+ # Sort agents by name for consistent display
88
+ agents = sorted(verification["agents_found"], key=lambda x: x.get('name', x.get('file', '')))
89
+
90
+ for agent in agents:
91
+ name = agent.get('name', 'unknown')
92
+ version = agent.get('version', 'unknown')
93
+ # Format: name (version)
94
+ output_lines.append(f" {name:<20} {version}")
95
+
96
+ # Add base agent version info
97
+ try:
98
+ import json
99
+ base_agent_path = deployment_service.base_agent_path
100
+ if base_agent_path.exists():
101
+ base_data = json.loads(base_agent_path.read_text())
102
+ # Parse version the same way as AgentDeploymentService
103
+ raw_version = base_data.get('base_version') or base_data.get('version', 0)
104
+ base_version_tuple = deployment_service._parse_version(raw_version)
105
+ base_version_str = deployment_service._format_version_display(base_version_tuple)
106
+ output_lines.append(f"\n Base Agent Version: {base_version_str}")
107
+ except:
108
+ pass
109
+
110
+ # Check for agents needing migration
111
+ if verification.get("agents_needing_migration"):
112
+ output_lines.append(f"\n ⚠️ {len(verification['agents_needing_migration'])} agent(s) need migration to semantic versioning")
113
+ output_lines.append(f" Run 'claude-mpm agents deploy' to update")
114
+
115
+ output_lines.append("-" * 40)
116
+ return "\n".join(output_lines)
117
+ except Exception as e:
118
+ # Log error but don't fail
119
+ logger = get_logger("cli")
120
+ logger.debug(f"Failed to get agent versions: {e}")
121
+ return None
122
+
123
+
124
+ def list_agent_versions_at_startup() -> None:
125
+ """
126
+ List deployed agent versions at startup.
127
+
128
+ WHY: Users want to see what agents are available when they start a session.
129
+ This provides immediate feedback about the deployed agent environment.
130
+ """
131
+ agent_versions = get_agent_versions_display()
132
+ if agent_versions:
133
+ print(agent_versions)
134
+ print() # Extra newline after the display
135
+
136
+
137
+ def setup_logging(args) -> object:
138
+ """
139
+ Set up logging based on parsed arguments.
140
+
141
+ WHY: This centralizes logging setup logic, handling the deprecated --debug flag
142
+ and the new --logging argument consistently across all commands.
143
+
144
+ Args:
145
+ args: Parsed command line arguments
146
+
147
+ Returns:
148
+ Logger instance
149
+ """
150
+ from ..core.logger import setup_logging as core_setup_logging, get_logger
151
+ from ..constants import LogLevel
152
+
153
+ # Handle deprecated --debug flag
154
+ if hasattr(args, 'debug') and args.debug and args.logging == LogLevel.INFO.value:
155
+ args.logging = LogLevel.DEBUG.value
156
+
157
+ # Only setup logging if not OFF
158
+ if args.logging != LogLevel.OFF.value:
159
+ logger = core_setup_logging(level=args.logging, log_dir=args.log_dir)
160
+ else:
161
+ # Minimal logger for CLI feedback
162
+ import logging
163
+ logger = logging.getLogger("cli")
164
+ logger.setLevel(logging.WARNING)
165
+
166
+ return logger
167
+
168
+
169
+ def ensure_directories() -> None:
170
+ """
171
+ Ensure required directories exist on first run.
172
+
173
+ WHY: Claude-mpm needs certain directories to function properly. Rather than
174
+ failing when they don't exist, we create them automatically for a better
175
+ user experience.
176
+ """
177
+ # Import ensure_directories using safe_import pattern
178
+ init_ensure_directories = safe_import(
179
+ 'claude_mpm.init',
180
+ None,
181
+ ['ensure_directories']
182
+ )
183
+
184
+ if init_ensure_directories:
185
+ try:
186
+ init_ensure_directories()
187
+ except Exception:
188
+ # Continue even if initialization fails
189
+ # The individual commands will handle missing directories as needed
190
+ pass
@@ -1,6 +1,25 @@
1
1
  """
2
2
  Enhanced CLI operations for claude-mpm.
3
3
 
4
+ WHY THIS FILE EXISTS:
5
+ This module provides an alternative CLI implementation with enhanced error handling
6
+ and validation features. It was created to explore advanced CLI patterns including:
7
+ - Comprehensive prerequisite validation
8
+ - User-friendly error messages with suggestions
9
+ - Dry-run mode for testing
10
+ - Profile validation and generation
11
+ - Rich terminal output with status indicators
12
+
13
+ CURRENT STATUS: This is an experimental/alternative CLI implementation that uses
14
+ Click instead of argparse. It's kept separate from the main CLI to:
15
+ 1. Preserve the existing CLI behavior
16
+ 2. Allow testing of new features without breaking the main interface
17
+ 3. Provide a reference implementation for future CLI enhancements
18
+
19
+ NOTE: This CLI is not currently used in production. The main CLI is in cli/__init__.py.
20
+ To use this enhanced CLI, you would need to create a separate entry point or
21
+ integrate selected features into the main CLI.
22
+
4
23
  Implements error handling and user guidance patterns from awesome-claude-code.
5
24
  """
6
25
 
@@ -20,10 +20,10 @@ import importlib.util
20
20
  from datetime import datetime
21
21
  from dataclasses import dataclass
22
22
 
23
- try:
24
- from ..core.logger import get_logger
25
- except ImportError:
26
- from core.logger import get_logger
23
+ from claude_mpm.utils.imports import safe_import
24
+
25
+ # Import logger using safe_import pattern
26
+ get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
27
27
 
28
28
 
29
29
  @dataclass
@@ -12,7 +12,7 @@ from typing import Any, Dict, Optional, Type, TypeVar
12
12
  from .container import DIContainer
13
13
  from .logger import get_logger
14
14
  from .config import Config
15
- from ..services.agent_deployment import AgentDeploymentService
15
+ from claude_mpm.services.agent_deployment import AgentDeploymentService
16
16
  from ..orchestration.factory import OrchestratorFactory
17
17
  from ..orchestration.base import BaseOrchestrator
18
18
 
@@ -38,7 +38,7 @@ class ServiceRegistry:
38
38
  """Register all core framework services."""
39
39
  from ..services.shared_prompt_cache import SharedPromptCache
40
40
  from ..services.ticket_manager import TicketManager
41
- from ..services.agent_deployment import AgentDeploymentService
41
+ from claude_mpm.services.agent_deployment import AgentDeploymentService
42
42
  from .session_manager import SessionManager
43
43
  from .agent_session_manager import AgentSessionManager
44
44
  from .config import Config
@@ -195,16 +195,15 @@ class SimpleClaudeRunner:
195
195
  clean_env.pop(var, None)
196
196
 
197
197
  # Set the correct working directory for Claude Code
198
- # If CLAUDE_MPM_USER_PWD is set, use that as the working directory
198
+ # WHY: We're already in the user's directory thanks to __main__.py restoration
199
+ # We just need to ensure CLAUDE_WORKSPACE is set correctly
200
+ current_dir = os.getcwd()
201
+ clean_env['CLAUDE_WORKSPACE'] = current_dir
202
+ self.logger.info(f"Working directory: {current_dir}")
203
+
204
+ # Log directory context for debugging
199
205
  if 'CLAUDE_MPM_USER_PWD' in clean_env:
200
- user_pwd = clean_env['CLAUDE_MPM_USER_PWD']
201
- clean_env['CLAUDE_WORKSPACE'] = user_pwd
202
- # Also change to that directory before launching Claude
203
- try:
204
- os.chdir(user_pwd)
205
- self.logger.info(f"Changed working directory to: {user_pwd}")
206
- except Exception as e:
207
- self.logger.warning(f"Could not change to user directory {user_pwd}: {e}")
206
+ self.logger.debug(f"User PWD from env: {clean_env['CLAUDE_MPM_USER_PWD']}")
208
207
 
209
208
  print("Launching Claude...")
210
209
 
@@ -313,19 +312,15 @@ class SimpleClaudeRunner:
313
312
  env = os.environ.copy()
314
313
 
315
314
  # Set the correct working directory for Claude Code
315
+ # WHY: We're already in the user's directory thanks to __main__.py restoration
316
+ # We just need to ensure CLAUDE_WORKSPACE is set correctly
317
+ current_dir = os.getcwd()
318
+ env['CLAUDE_WORKSPACE'] = current_dir
319
+ self.logger.info(f"Working directory for subprocess: {current_dir}")
320
+
321
+ # Log directory context for debugging
316
322
  if 'CLAUDE_MPM_USER_PWD' in env:
317
- user_pwd = env['CLAUDE_MPM_USER_PWD']
318
- env['CLAUDE_WORKSPACE'] = user_pwd
319
- # Change to that directory before running Claude
320
- try:
321
- original_cwd = os.getcwd()
322
- os.chdir(user_pwd)
323
- self.logger.info(f"Changed working directory to: {user_pwd}")
324
- except Exception as e:
325
- self.logger.warning(f"Could not change to user directory {user_pwd}: {e}")
326
- original_cwd = None
327
- else:
328
- original_cwd = None
323
+ self.logger.debug(f"User PWD from env: {env['CLAUDE_MPM_USER_PWD']}")
329
324
 
330
325
  # Run Claude
331
326
  if self.project_logger:
@@ -337,12 +332,7 @@ class SimpleClaudeRunner:
337
332
 
338
333
  result = subprocess.run(cmd, capture_output=True, text=True, env=env)
339
334
 
340
- # Restore original directory if we changed it
341
- if original_cwd:
342
- try:
343
- os.chdir(original_cwd)
344
- except Exception:
345
- pass
335
+ # No need to restore directory since we didn't change it
346
336
  execution_time = time.time() - start_time
347
337
 
348
338
  if result.returncode == 0:
@@ -44,6 +44,7 @@ project_root = Path(__file__).parent.parent.parent.parent
44
44
  sys.path.insert(0, str(project_root / 'src'))
45
45
 
46
46
  from claude_mpm.core.logger import get_logger, setup_logging, LogLevel
47
+ from claude_mpm.security import BashSecurityValidator
47
48
 
48
49
  # Don't initialize global logging here - we'll do it per-project
49
50
  logger = None
@@ -329,6 +330,7 @@ class ClaudeHookHandler:
329
330
  - Checking for path traversal attempts
330
331
  - Ensuring file operations stay within the working directory
331
332
  - Blocking dangerous operations
333
+ - Validating bash commands for security violations
332
334
 
333
335
  Security Design:
334
336
  - Fail-secure: Block suspicious operations
@@ -342,6 +344,29 @@ class ClaudeHookHandler:
342
344
  tool_name = self.event.get('tool_name', '')
343
345
  tool_input = self.event.get('tool_input', {})
344
346
 
347
+ # Get the working directory from the event
348
+ working_dir = Path(self.event.get('cwd', os.getcwd())).resolve()
349
+
350
+ # Special handling for Bash tool - validate commands for security
351
+ if tool_name == 'Bash':
352
+ command = tool_input.get('command', '')
353
+ if command:
354
+ # Create bash validator for this working directory
355
+ bash_validator = BashSecurityValidator(working_dir)
356
+ is_valid, error_msg = bash_validator.validate_command(command)
357
+
358
+ if not is_valid:
359
+ if logger:
360
+ logger.warning(f"Security: Blocked Bash command: {command[:100]}...")
361
+
362
+ response = {
363
+ "action": "block",
364
+ "error": error_msg
365
+ }
366
+ print(json.dumps(response))
367
+ sys.exit(0)
368
+ return
369
+
345
370
  # List of tools that perform write operations
346
371
  # These tools need special security checks to prevent
347
372
  # writing outside the project directory
@@ -349,9 +374,6 @@ class ClaudeHookHandler:
349
374
 
350
375
  # Check if this is a write operation
351
376
  if tool_name in write_tools:
352
- # Get the working directory from the event
353
- working_dir = Path(self.event.get('cwd', os.getcwd())).resolve()
354
-
355
377
  # Extract file path based on tool type
356
378
  file_path = None
357
379
  if tool_name in ['Write', 'Edit', 'NotebookEdit']:
@@ -414,7 +436,34 @@ class ClaudeHookHandler:
414
436
  sys.exit(0)
415
437
  return
416
438
 
417
- # For read operations and other tools, continue normally
439
+ # Additional security checks for other tools that might access files
440
+ read_tools = ['Read', 'LS', 'Glob', 'Grep', 'NotebookRead']
441
+
442
+ if tool_name in read_tools:
443
+ # For read operations, we allow them but log for auditing
444
+ file_path = None
445
+ if tool_name == 'Read':
446
+ file_path = tool_input.get('file_path')
447
+ elif tool_name == 'NotebookRead':
448
+ file_path = tool_input.get('notebook_path')
449
+ elif tool_name == 'LS':
450
+ file_path = tool_input.get('path')
451
+ elif tool_name in ['Glob', 'Grep']:
452
+ file_path = tool_input.get('path', '.')
453
+
454
+ if file_path and logger:
455
+ # Log read operations for security auditing
456
+ logger.info(f"Security audit: {tool_name} accessing path: {file_path}")
457
+
458
+ # Check for other potentially dangerous tools
459
+ if tool_name in ['WebFetch', 'WebSearch']:
460
+ # Log web access for security auditing
461
+ if logger:
462
+ url = tool_input.get('url', '') if tool_name == 'WebFetch' else 'N/A'
463
+ query = tool_input.get('query', '') if tool_name == 'WebSearch' else 'N/A'
464
+ logger.info(f"Security audit: {tool_name} - URL: {url}, Query: {query}")
465
+
466
+ # For all other operations, continue normally
418
467
  return self._continue()
419
468
 
420
469
  def _handle_post_tool_use(self):
@@ -0,0 +1,106 @@
1
+ """
2
+ Claude MPM Models Package
3
+ =========================
4
+
5
+ Centralized data models for the Claude MPM system.
6
+
7
+ WHY: This package centralizes all data models to:
8
+ - Prevent circular imports
9
+ - Reduce code duplication
10
+ - Ensure consistent data structures
11
+ - Make models easily discoverable
12
+
13
+ DESIGN DECISION: Models are organized by domain:
14
+ - agent_definition: Core agent behavior models
15
+ - lifecycle: Agent lifecycle management models
16
+ - modification: Change tracking models
17
+ - persistence: Storage operation models
18
+ - registry: Discovery and management models
19
+ - common: Shared constants and enums
20
+ """
21
+
22
+ # Agent definition models
23
+ from .agent_definition import (
24
+ AgentType,
25
+ AgentSection,
26
+ AgentPermissions,
27
+ AgentWorkflow,
28
+ AgentMetadata,
29
+ AgentDefinition
30
+ )
31
+
32
+ # Lifecycle models
33
+ from .lifecycle import (
34
+ LifecycleOperation,
35
+ LifecycleState,
36
+ AgentLifecycleRecord,
37
+ LifecycleOperationResult
38
+ )
39
+
40
+ # Modification models
41
+ from .modification import (
42
+ ModificationType,
43
+ ModificationTier,
44
+ AgentModification,
45
+ ModificationHistory
46
+ )
47
+
48
+ # Persistence models
49
+ from .persistence import (
50
+ PersistenceStrategy,
51
+ PersistenceOperation,
52
+ PersistenceRecord
53
+ )
54
+
55
+ # Registry models
56
+ from .registry import (
57
+ AgentTier,
58
+ AgentRegistryMetadata
59
+ )
60
+
61
+ # Common constants
62
+ from .common import (
63
+ AGENT_FILE_EXTENSIONS,
64
+ AGENT_IGNORE_PATTERNS,
65
+ CORE_AGENT_TYPES,
66
+ SPECIALIZED_AGENT_TYPES,
67
+ ALL_AGENT_TYPES
68
+ )
69
+
70
+ __all__ = [
71
+ # Agent definition
72
+ 'AgentType',
73
+ 'AgentSection',
74
+ 'AgentPermissions',
75
+ 'AgentWorkflow',
76
+ 'AgentMetadata',
77
+ 'AgentDefinition',
78
+
79
+ # Lifecycle
80
+ 'LifecycleOperation',
81
+ 'LifecycleState',
82
+ 'AgentLifecycleRecord',
83
+ 'LifecycleOperationResult',
84
+
85
+ # Modification
86
+ 'ModificationType',
87
+ 'ModificationTier',
88
+ 'AgentModification',
89
+ 'ModificationHistory',
90
+
91
+ # Persistence
92
+ 'PersistenceStrategy',
93
+ 'PersistenceOperation',
94
+ 'PersistenceRecord',
95
+
96
+ # Registry
97
+ 'AgentTier',
98
+ 'AgentRegistryMetadata',
99
+
100
+ # Common
101
+ 'AGENT_FILE_EXTENSIONS',
102
+ 'AGENT_IGNORE_PATTERNS',
103
+ 'CORE_AGENT_TYPES',
104
+ 'SPECIALIZED_AGENT_TYPES',
105
+ 'ALL_AGENT_TYPES'
106
+ ]