claude-mpm 0.3.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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Argument registry for consolidating CLI argument definitions."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any, Optional, Callable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ArgumentRegistry:
|
|
9
|
+
"""Registry for managing CLI argument definitions.
|
|
10
|
+
|
|
11
|
+
This class consolidates argument definitions to reduce duplication
|
|
12
|
+
and simplify the main CLI function.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
"""Initialize the registry with common argument groups."""
|
|
17
|
+
self._arguments: Dict[str, Dict[str, Any]] = {}
|
|
18
|
+
self._groups: Dict[str, list] = {
|
|
19
|
+
'global': [],
|
|
20
|
+
'run': [],
|
|
21
|
+
'logging': [],
|
|
22
|
+
'framework': [],
|
|
23
|
+
'orchestration': []
|
|
24
|
+
}
|
|
25
|
+
self._register_common_arguments()
|
|
26
|
+
|
|
27
|
+
def _register_common_arguments(self):
|
|
28
|
+
"""Register all common argument definitions."""
|
|
29
|
+
# Version argument
|
|
30
|
+
self.register('version', {
|
|
31
|
+
'flags': ['--version'],
|
|
32
|
+
'action': 'version',
|
|
33
|
+
'help': 'Show version and exit',
|
|
34
|
+
'groups': ['global']
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
# Logging arguments
|
|
38
|
+
self.register('debug', {
|
|
39
|
+
'flags': ['-d', '--debug'],
|
|
40
|
+
'action': 'store_true',
|
|
41
|
+
'help': 'Enable debug logging (deprecated, use --logging DEBUG)',
|
|
42
|
+
'groups': ['logging', 'global']
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
self.register('logging', {
|
|
46
|
+
'flags': ['--logging'],
|
|
47
|
+
'choices': ['OFF', 'INFO', 'DEBUG'],
|
|
48
|
+
'default': 'OFF',
|
|
49
|
+
'help': 'Logging level (default: OFF)',
|
|
50
|
+
'groups': ['logging', 'global']
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
self.register('log_dir', {
|
|
54
|
+
'flags': ['--log-dir'],
|
|
55
|
+
'type': Path,
|
|
56
|
+
'help': 'Custom log directory (default: ~/.claude-mpm/logs)',
|
|
57
|
+
'groups': ['logging', 'global']
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
# Framework arguments
|
|
61
|
+
self.register('framework_path', {
|
|
62
|
+
'flags': ['--framework-path'],
|
|
63
|
+
'type': Path,
|
|
64
|
+
'help': 'Path to claude-mpm framework',
|
|
65
|
+
'groups': ['framework', 'global']
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
self.register('agents_dir', {
|
|
69
|
+
'flags': ['--agents-dir'],
|
|
70
|
+
'type': Path,
|
|
71
|
+
'help': 'Custom agents directory to use',
|
|
72
|
+
'groups': ['framework', 'global']
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
self.register('no_hooks', {
|
|
76
|
+
'flags': ['--no-hooks'],
|
|
77
|
+
'action': 'store_true',
|
|
78
|
+
'help': 'Disable hook service (runs without hooks)',
|
|
79
|
+
'groups': ['framework', 'global', 'run']
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
# Run command arguments
|
|
83
|
+
self.register('no_tickets', {
|
|
84
|
+
'flags': ['--no-tickets'],
|
|
85
|
+
'action': 'store_true',
|
|
86
|
+
'help': 'Disable automatic ticket creation',
|
|
87
|
+
'groups': ['run']
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
self.register('input', {
|
|
91
|
+
'flags': ['-i', '--input'],
|
|
92
|
+
'type': str,
|
|
93
|
+
'help': 'Input text or file path (for non-interactive mode)',
|
|
94
|
+
'groups': ['run']
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
self.register('non_interactive', {
|
|
98
|
+
'flags': ['--non-interactive'],
|
|
99
|
+
'action': 'store_true',
|
|
100
|
+
'help': 'Run in non-interactive mode (read from stdin or --input)',
|
|
101
|
+
'groups': ['run']
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
# Orchestration arguments
|
|
105
|
+
self.register('subprocess', {
|
|
106
|
+
'flags': ['--subprocess'],
|
|
107
|
+
'action': 'store_true',
|
|
108
|
+
'help': 'Use subprocess orchestration for agent delegations',
|
|
109
|
+
'groups': ['orchestration', 'run']
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
self.register('interactive_subprocess', {
|
|
113
|
+
'flags': ['--interactive-subprocess'],
|
|
114
|
+
'action': 'store_true',
|
|
115
|
+
'help': 'Use interactive subprocess orchestration with pexpect control',
|
|
116
|
+
'groups': ['orchestration', 'run']
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
self.register('todo_hijack', {
|
|
120
|
+
'flags': ['--todo-hijack'],
|
|
121
|
+
'action': 'store_true',
|
|
122
|
+
'help': 'Enable TODO hijacking to transform Claude\'s TODOs into agent delegations',
|
|
123
|
+
'groups': ['orchestration', 'run']
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
# Ticket list arguments
|
|
127
|
+
self.register('limit', {
|
|
128
|
+
'flags': ['-n', '--limit'],
|
|
129
|
+
'type': int,
|
|
130
|
+
'default': 10,
|
|
131
|
+
'help': 'Number of tickets to show',
|
|
132
|
+
'groups': ['tickets']
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
def register(self, name: str, definition: Dict[str, Any]):
|
|
136
|
+
"""Register a new argument definition.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
name: Internal name for the argument
|
|
140
|
+
definition: Dictionary containing argument configuration:
|
|
141
|
+
- flags: List of flag strings (e.g., ['-d', '--debug'])
|
|
142
|
+
- groups: List of group names this argument belongs to
|
|
143
|
+
- All other kwargs are passed to add_argument()
|
|
144
|
+
"""
|
|
145
|
+
self._arguments[name] = definition
|
|
146
|
+
|
|
147
|
+
# Add to groups
|
|
148
|
+
for group in definition.get('groups', []):
|
|
149
|
+
if group not in self._groups:
|
|
150
|
+
self._groups[group] = []
|
|
151
|
+
self._groups[group].append(name)
|
|
152
|
+
|
|
153
|
+
def apply_arguments(self, parser: argparse.ArgumentParser,
|
|
154
|
+
groups: Optional[list] = None,
|
|
155
|
+
exclude: Optional[list] = None):
|
|
156
|
+
"""Apply argument definitions to a parser.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
parser: ArgumentParser or subparser to add arguments to
|
|
160
|
+
groups: List of group names to include (None = all)
|
|
161
|
+
exclude: List of argument names to exclude
|
|
162
|
+
"""
|
|
163
|
+
# Determine which arguments to add
|
|
164
|
+
if groups is None:
|
|
165
|
+
arg_names = list(self._arguments.keys())
|
|
166
|
+
else:
|
|
167
|
+
arg_names = []
|
|
168
|
+
for group in groups:
|
|
169
|
+
arg_names.extend(self._groups.get(group, []))
|
|
170
|
+
# Remove duplicates while preserving order
|
|
171
|
+
seen = set()
|
|
172
|
+
arg_names = [x for x in arg_names if not (x in seen or seen.add(x))]
|
|
173
|
+
|
|
174
|
+
# Apply exclusions
|
|
175
|
+
if exclude:
|
|
176
|
+
arg_names = [n for n in arg_names if n not in exclude]
|
|
177
|
+
|
|
178
|
+
# Add arguments to parser
|
|
179
|
+
for name in arg_names:
|
|
180
|
+
definition = self._arguments[name].copy()
|
|
181
|
+
flags = definition.pop('flags')
|
|
182
|
+
definition.pop('groups', None) # Remove groups from kwargs
|
|
183
|
+
|
|
184
|
+
# Special handling for version
|
|
185
|
+
if name == 'version' and hasattr(parser, '_version'):
|
|
186
|
+
definition['version'] = parser._version
|
|
187
|
+
|
|
188
|
+
parser.add_argument(*flags, **definition)
|
|
189
|
+
|
|
190
|
+
def get_argument_groups(self) -> Dict[str, list]:
|
|
191
|
+
"""Get all argument groups and their members."""
|
|
192
|
+
return self._groups.copy()
|
|
193
|
+
|
|
194
|
+
def get_argument_definition(self, name: str) -> Optional[Dict[str, Any]]:
|
|
195
|
+
"""Get the definition for a specific argument."""
|
|
196
|
+
return self._arguments.get(name, {}).copy()
|
|
197
|
+
|
|
198
|
+
def create_argument_group(self, parser: argparse.ArgumentParser,
|
|
199
|
+
group_name: str,
|
|
200
|
+
title: str,
|
|
201
|
+
description: Optional[str] = None):
|
|
202
|
+
"""Create an argument group and add arguments from a named group.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
parser: Parser to add the group to
|
|
206
|
+
group_name: Name of the argument group in registry
|
|
207
|
+
title: Title for the argument group
|
|
208
|
+
description: Optional description for the group
|
|
209
|
+
"""
|
|
210
|
+
group = parser.add_argument_group(title, description)
|
|
211
|
+
|
|
212
|
+
# Get arguments for this group
|
|
213
|
+
arg_names = self._groups.get(group_name, [])
|
|
214
|
+
|
|
215
|
+
for name in arg_names:
|
|
216
|
+
definition = self._arguments[name].copy()
|
|
217
|
+
flags = definition.pop('flags')
|
|
218
|
+
definition.pop('groups', None)
|
|
219
|
+
|
|
220
|
+
group.add_argument(*flags, **definition)
|
|
221
|
+
|
|
222
|
+
return group
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Command registry for managing CLI subcommands."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Dict, Any, Optional, Callable, List
|
|
5
|
+
from .args import ArgumentRegistry
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CommandDefinition:
|
|
9
|
+
"""Definition for a CLI command."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, name: str, help_text: str,
|
|
12
|
+
handler: Callable,
|
|
13
|
+
argument_groups: Optional[List[str]] = None,
|
|
14
|
+
extra_args: Optional[Dict[str, Dict[str, Any]]] = None):
|
|
15
|
+
"""Initialize command definition.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
name: Command name
|
|
19
|
+
help_text: Help text for the command
|
|
20
|
+
handler: Function to call when command is executed
|
|
21
|
+
argument_groups: List of argument group names to include
|
|
22
|
+
extra_args: Additional command-specific arguments
|
|
23
|
+
"""
|
|
24
|
+
self.name = name
|
|
25
|
+
self.help_text = help_text
|
|
26
|
+
self.handler = handler
|
|
27
|
+
self.argument_groups = argument_groups or []
|
|
28
|
+
self.extra_args = extra_args or {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CommandRegistry:
|
|
32
|
+
"""Registry for managing CLI subcommands.
|
|
33
|
+
|
|
34
|
+
This class simplifies subcommand management and reduces
|
|
35
|
+
complexity in the main CLI function.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, arg_registry: Optional[ArgumentRegistry] = None):
|
|
39
|
+
"""Initialize the command registry.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
arg_registry: ArgumentRegistry instance for applying arguments
|
|
43
|
+
"""
|
|
44
|
+
self._commands: Dict[str, CommandDefinition] = {}
|
|
45
|
+
self._arg_registry = arg_registry or ArgumentRegistry()
|
|
46
|
+
self._default_command: Optional[str] = None
|
|
47
|
+
|
|
48
|
+
def register(self, name: str, help_text: str, handler: Callable,
|
|
49
|
+
argument_groups: Optional[List[str]] = None,
|
|
50
|
+
extra_args: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
51
|
+
is_default: bool = False):
|
|
52
|
+
"""Register a new command.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
name: Command name
|
|
56
|
+
help_text: Help text for the command
|
|
57
|
+
handler: Function to call when command is executed
|
|
58
|
+
argument_groups: List of argument group names from ArgumentRegistry
|
|
59
|
+
extra_args: Additional command-specific arguments
|
|
60
|
+
is_default: Whether this is the default command
|
|
61
|
+
"""
|
|
62
|
+
command = CommandDefinition(
|
|
63
|
+
name=name,
|
|
64
|
+
help_text=help_text,
|
|
65
|
+
handler=handler,
|
|
66
|
+
argument_groups=argument_groups,
|
|
67
|
+
extra_args=extra_args
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self._commands[name] = command
|
|
71
|
+
|
|
72
|
+
if is_default:
|
|
73
|
+
self._default_command = name
|
|
74
|
+
|
|
75
|
+
def setup_subcommands(self, parser: argparse.ArgumentParser) -> argparse._SubParsersAction:
|
|
76
|
+
"""Set up all registered subcommands on the parser.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
parser: Main argument parser
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The subparsers object
|
|
83
|
+
"""
|
|
84
|
+
subparsers = parser.add_subparsers(
|
|
85
|
+
dest="command",
|
|
86
|
+
help="Available commands"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
for name, command in self._commands.items():
|
|
90
|
+
# Create subparser
|
|
91
|
+
subparser = subparsers.add_parser(
|
|
92
|
+
name,
|
|
93
|
+
help=command.help_text
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Apply argument groups
|
|
97
|
+
if command.argument_groups:
|
|
98
|
+
self._arg_registry.apply_arguments(
|
|
99
|
+
subparser,
|
|
100
|
+
groups=command.argument_groups
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Apply extra arguments
|
|
104
|
+
for arg_name, arg_config in command.extra_args.items():
|
|
105
|
+
flags = arg_config.pop('flags', [f'--{arg_name.replace("_", "-")}'])
|
|
106
|
+
subparser.add_argument(*flags, **arg_config)
|
|
107
|
+
|
|
108
|
+
# Store handler reference
|
|
109
|
+
subparser.set_defaults(_handler=command.handler)
|
|
110
|
+
|
|
111
|
+
return subparsers
|
|
112
|
+
|
|
113
|
+
def execute_command(self, args: argparse.Namespace, **kwargs) -> Any:
|
|
114
|
+
"""Execute the appropriate command handler.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
args: Parsed command line arguments
|
|
118
|
+
**kwargs: Additional keyword arguments to pass to handler
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Result from the command handler
|
|
122
|
+
"""
|
|
123
|
+
# Handle default command
|
|
124
|
+
command_name = args.command
|
|
125
|
+
if not command_name and self._default_command:
|
|
126
|
+
command_name = self._default_command
|
|
127
|
+
args.command = command_name
|
|
128
|
+
|
|
129
|
+
# Apply default command's argument defaults
|
|
130
|
+
command = self._commands.get(command_name)
|
|
131
|
+
if command:
|
|
132
|
+
# Set defaults for arguments that might not be present
|
|
133
|
+
for group in command.argument_groups:
|
|
134
|
+
for arg_name in self._arg_registry._groups.get(group, []):
|
|
135
|
+
if not hasattr(args, arg_name):
|
|
136
|
+
definition = self._arg_registry.get_argument_definition(arg_name)
|
|
137
|
+
if definition and 'default' in definition:
|
|
138
|
+
setattr(args, arg_name, definition['default'])
|
|
139
|
+
elif definition and definition.get('action') == 'store_true':
|
|
140
|
+
setattr(args, arg_name, False)
|
|
141
|
+
|
|
142
|
+
# Get command handler
|
|
143
|
+
if hasattr(args, '_handler'):
|
|
144
|
+
handler = args._handler
|
|
145
|
+
else:
|
|
146
|
+
command = self._commands.get(command_name)
|
|
147
|
+
if command:
|
|
148
|
+
handler = command.handler
|
|
149
|
+
else:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
# Execute handler
|
|
153
|
+
return handler(args, **kwargs)
|
|
154
|
+
|
|
155
|
+
def get_command_names(self) -> List[str]:
|
|
156
|
+
"""Get list of all registered command names."""
|
|
157
|
+
return list(self._commands.keys())
|
|
158
|
+
|
|
159
|
+
def get_default_command(self) -> Optional[str]:
|
|
160
|
+
"""Get the default command name."""
|
|
161
|
+
return self._default_command
|
|
162
|
+
|
|
163
|
+
def has_command(self, name: str) -> bool:
|
|
164
|
+
"""Check if a command is registered."""
|
|
165
|
+
return name in self._commands
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def register_standard_commands(registry: CommandRegistry):
|
|
169
|
+
"""Register the standard claude-mpm commands.
|
|
170
|
+
|
|
171
|
+
This function registers all the standard commands that claude-mpm
|
|
172
|
+
supports, reducing the setup code in main().
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
registry: CommandRegistry instance to register commands with
|
|
176
|
+
"""
|
|
177
|
+
# Import handlers (these would be refactored from cli.py)
|
|
178
|
+
from ..cli import run_session, list_tickets, show_info
|
|
179
|
+
|
|
180
|
+
# Register run command (default)
|
|
181
|
+
registry.register(
|
|
182
|
+
name='run',
|
|
183
|
+
help_text='Run orchestrated Claude session (default)',
|
|
184
|
+
handler=run_session,
|
|
185
|
+
argument_groups=['run', 'orchestration'],
|
|
186
|
+
is_default=True
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Register tickets command
|
|
190
|
+
registry.register(
|
|
191
|
+
name='tickets',
|
|
192
|
+
help_text='List recent tickets',
|
|
193
|
+
handler=list_tickets,
|
|
194
|
+
argument_groups=['tickets']
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Register info command
|
|
198
|
+
registry.register(
|
|
199
|
+
name='info',
|
|
200
|
+
help_text='Show framework and configuration info',
|
|
201
|
+
handler=show_info,
|
|
202
|
+
argument_groups=[] # No specific arguments needed
|
|
203
|
+
)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Example migration showing how to refactor main() using registries.
|
|
2
|
+
|
|
3
|
+
This file demonstrates how to reduce the complexity of the main() function
|
|
4
|
+
from 16 to under 10 by using ArgumentRegistry and CommandRegistry.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from claude_mpm._version import __version__
|
|
13
|
+
from claude_mpm.core.logger import get_logger, setup_logging
|
|
14
|
+
from claude_mpm.services.hook_service_manager import HookServiceManager
|
|
15
|
+
from claude_mpm.cli import ArgumentRegistry, CommandRegistry, register_standard_commands
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main_refactored(argv: Optional[list] = None):
|
|
19
|
+
"""Refactored main CLI entry point with reduced complexity.
|
|
20
|
+
|
|
21
|
+
This version uses registries to manage arguments and commands,
|
|
22
|
+
reducing cyclomatic complexity from 16 to approximately 8.
|
|
23
|
+
"""
|
|
24
|
+
# Initialize registries
|
|
25
|
+
arg_registry = ArgumentRegistry()
|
|
26
|
+
cmd_registry = CommandRegistry(arg_registry)
|
|
27
|
+
|
|
28
|
+
# Register standard commands
|
|
29
|
+
register_standard_commands(cmd_registry)
|
|
30
|
+
|
|
31
|
+
# Create parser with basic info
|
|
32
|
+
parser = argparse.ArgumentParser(
|
|
33
|
+
prog="claude-mpm",
|
|
34
|
+
description=f"Claude Multi-Agent Project Manager v{__version__} - Orchestrate Claude with agent delegation and ticket tracking",
|
|
35
|
+
epilog="By default, runs an orchestrated Claude session. Use 'claude-mpm' for interactive mode or 'claude-mpm -i \"prompt\"' for non-interactive mode."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Store version for ArgumentRegistry
|
|
39
|
+
parser._version = f"claude-mpm {__version__}"
|
|
40
|
+
|
|
41
|
+
# Apply global arguments
|
|
42
|
+
arg_registry.apply_arguments(parser, groups=['global'])
|
|
43
|
+
|
|
44
|
+
# Apply run arguments at top level (for default behavior)
|
|
45
|
+
arg_registry.apply_arguments(parser, groups=['run'], exclude=['no_hooks'])
|
|
46
|
+
|
|
47
|
+
# Set up subcommands
|
|
48
|
+
cmd_registry.setup_subcommands(parser)
|
|
49
|
+
|
|
50
|
+
# Parse arguments
|
|
51
|
+
args = parser.parse_args(argv)
|
|
52
|
+
|
|
53
|
+
# Set up logging
|
|
54
|
+
_setup_logging(args)
|
|
55
|
+
|
|
56
|
+
# Initialize hook service
|
|
57
|
+
hook_manager = _initialize_hook_service(args)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
# Execute command
|
|
61
|
+
result = cmd_registry.execute_command(args, hook_manager=hook_manager)
|
|
62
|
+
if result is None and not args.command:
|
|
63
|
+
parser.print_help()
|
|
64
|
+
return 1
|
|
65
|
+
return result or 0
|
|
66
|
+
|
|
67
|
+
except KeyboardInterrupt:
|
|
68
|
+
get_logger("cli").info("Session interrupted by user")
|
|
69
|
+
return 0
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger = get_logger("cli")
|
|
72
|
+
logger.error(f"Error: {e}")
|
|
73
|
+
if args.debug:
|
|
74
|
+
import traceback
|
|
75
|
+
traceback.print_exc()
|
|
76
|
+
return 1
|
|
77
|
+
finally:
|
|
78
|
+
# Clean up hook service
|
|
79
|
+
if hook_manager:
|
|
80
|
+
hook_manager.stop_service()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _setup_logging(args):
|
|
84
|
+
"""Set up logging based on arguments (extracted helper)."""
|
|
85
|
+
# Handle deprecated --debug flag
|
|
86
|
+
if args.debug and args.logging == "OFF":
|
|
87
|
+
args.logging = "DEBUG"
|
|
88
|
+
|
|
89
|
+
# Only setup logging if not OFF
|
|
90
|
+
if args.logging != "OFF":
|
|
91
|
+
setup_logging(level=args.logging, log_dir=args.log_dir)
|
|
92
|
+
else:
|
|
93
|
+
# Minimal logger for CLI feedback
|
|
94
|
+
import logging
|
|
95
|
+
logger = logging.getLogger("cli")
|
|
96
|
+
logger.setLevel(logging.WARNING)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _initialize_hook_service(args):
|
|
100
|
+
"""Initialize hook service if enabled (extracted helper)."""
|
|
101
|
+
if getattr(args, 'no_hooks', False):
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
# Check if hooks are enabled via config
|
|
106
|
+
from claude_mpm.config.hook_config import HookConfig
|
|
107
|
+
|
|
108
|
+
if not HookConfig.is_hooks_enabled():
|
|
109
|
+
get_logger("cli").info("Hooks disabled via configuration")
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
hook_manager = HookServiceManager(log_dir=args.log_dir)
|
|
113
|
+
if hook_manager.start_service():
|
|
114
|
+
logger = get_logger("cli")
|
|
115
|
+
logger.info(f"Hook service started on port {hook_manager.port}")
|
|
116
|
+
print(f"Hook service started on port {hook_manager.port}")
|
|
117
|
+
return hook_manager
|
|
118
|
+
else:
|
|
119
|
+
logger = get_logger("cli")
|
|
120
|
+
logger.warning("Failed to start hook service, continuing without hooks")
|
|
121
|
+
print("Failed to start hook service, continuing without hooks")
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
get_logger("cli").warning(f"Hook service initialization failed: {e}, continuing without hooks")
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# Example of how to add custom commands
|
|
130
|
+
def extend_with_custom_commands(cmd_registry: CommandRegistry):
|
|
131
|
+
"""Example of adding custom commands to the registry."""
|
|
132
|
+
|
|
133
|
+
def validate_command(args, **kwargs):
|
|
134
|
+
"""Custom validation command."""
|
|
135
|
+
print("Running validation...")
|
|
136
|
+
# Implementation here
|
|
137
|
+
return 0
|
|
138
|
+
|
|
139
|
+
# Register custom command
|
|
140
|
+
cmd_registry.register(
|
|
141
|
+
name='validate',
|
|
142
|
+
help_text='Validate project configuration and agent setup',
|
|
143
|
+
handler=validate_command,
|
|
144
|
+
argument_groups=['framework'],
|
|
145
|
+
extra_args={
|
|
146
|
+
'strict': {
|
|
147
|
+
'flags': ['--strict'],
|
|
148
|
+
'action': 'store_true',
|
|
149
|
+
'help': 'Enable strict validation mode'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Comparison with original main() function
|
|
156
|
+
def complexity_comparison():
|
|
157
|
+
"""Show the complexity reduction achieved.
|
|
158
|
+
|
|
159
|
+
Original main() function:
|
|
160
|
+
- Cyclomatic complexity: 16
|
|
161
|
+
- Lines of code: ~230
|
|
162
|
+
- Duplicate argument definitions: 6 (for 'run' command)
|
|
163
|
+
- Mixed concerns: argument parsing, logging, hook service, command dispatch
|
|
164
|
+
|
|
165
|
+
Refactored main_refactored() function:
|
|
166
|
+
- Cyclomatic complexity: ~8
|
|
167
|
+
- Lines of code: ~50
|
|
168
|
+
- Duplicate argument definitions: 0
|
|
169
|
+
- Separated concerns: registries handle definitions, helpers handle setup
|
|
170
|
+
|
|
171
|
+
Benefits:
|
|
172
|
+
1. Centralized argument definitions eliminate duplication
|
|
173
|
+
2. Command registry simplifies adding new commands
|
|
174
|
+
3. Extracted helpers reduce nesting and conditionals
|
|
175
|
+
4. Easier to test individual components
|
|
176
|
+
5. Clear separation of concerns
|
|
177
|
+
"""
|
|
178
|
+
pass
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
# Example usage of refactored main
|
|
183
|
+
sys.exit(main_refactored())
|