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
claude_mpm/init.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Initialization module for claude-mpm.
|
|
2
|
+
|
|
3
|
+
Handles creation of necessary directories and configuration files.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, Dict, Any
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
from claude_mpm.core.logger import get_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProjectInitializer:
|
|
16
|
+
"""Handles initialization of claude-mpm directories and configuration."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.logger = get_logger("initializer")
|
|
20
|
+
self.user_dir = Path.home() / ".claude-mpm"
|
|
21
|
+
self.project_dir = None
|
|
22
|
+
|
|
23
|
+
def initialize_user_directory(self) -> bool:
|
|
24
|
+
"""Initialize user-level .claude-mpm directory structure.
|
|
25
|
+
|
|
26
|
+
Creates:
|
|
27
|
+
- ~/.claude-mpm/
|
|
28
|
+
- agents/
|
|
29
|
+
- user-defined/
|
|
30
|
+
- config/
|
|
31
|
+
- logs/
|
|
32
|
+
- templates/
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
# Create main user directory
|
|
36
|
+
self.user_dir.mkdir(exist_ok=True)
|
|
37
|
+
|
|
38
|
+
# Create subdirectories
|
|
39
|
+
directories = [
|
|
40
|
+
self.user_dir / "agents" / "user-defined",
|
|
41
|
+
self.user_dir / "config",
|
|
42
|
+
self.user_dir / "logs",
|
|
43
|
+
self.user_dir / "templates",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
for directory in directories:
|
|
47
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
|
|
49
|
+
# Create default configuration if it doesn't exist
|
|
50
|
+
config_file = self.user_dir / "config" / "settings.json"
|
|
51
|
+
if not config_file.exists():
|
|
52
|
+
self._create_default_config(config_file)
|
|
53
|
+
|
|
54
|
+
# Copy agent templates if they don't exist
|
|
55
|
+
self._copy_agent_templates()
|
|
56
|
+
|
|
57
|
+
self.logger.info(f"Initialized user directory at {self.user_dir}")
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
self.logger.error(f"Failed to initialize user directory: {e}")
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
def initialize_project_directory(self, project_path: Optional[Path] = None) -> bool:
|
|
65
|
+
"""Initialize project-level .claude-mpm directory structure.
|
|
66
|
+
|
|
67
|
+
Creates:
|
|
68
|
+
- .claude-mpm/
|
|
69
|
+
- agents/
|
|
70
|
+
- project-specific/
|
|
71
|
+
- config/
|
|
72
|
+
- logs/
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
# Find project root
|
|
76
|
+
if project_path:
|
|
77
|
+
self.project_dir = project_path / ".claude-mpm"
|
|
78
|
+
else:
|
|
79
|
+
project_root = self._find_project_root()
|
|
80
|
+
if not project_root:
|
|
81
|
+
project_root = Path.cwd()
|
|
82
|
+
self.project_dir = project_root / ".claude-mpm"
|
|
83
|
+
|
|
84
|
+
# Create project directory
|
|
85
|
+
self.project_dir.mkdir(exist_ok=True)
|
|
86
|
+
|
|
87
|
+
# Create subdirectories
|
|
88
|
+
directories = [
|
|
89
|
+
self.project_dir / "agents" / "project-specific",
|
|
90
|
+
self.project_dir / "config",
|
|
91
|
+
self.project_dir / "logs",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
for directory in directories:
|
|
95
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
# Create project configuration
|
|
98
|
+
config_file = self.project_dir / "config" / "project.json"
|
|
99
|
+
if not config_file.exists():
|
|
100
|
+
self._create_project_config(config_file)
|
|
101
|
+
|
|
102
|
+
# Create .gitignore for project directory
|
|
103
|
+
gitignore = self.project_dir / ".gitignore"
|
|
104
|
+
if not gitignore.exists():
|
|
105
|
+
gitignore.write_text("logs/\n*.log\n*.pyc\n__pycache__/\n")
|
|
106
|
+
|
|
107
|
+
self.logger.info(f"Initialized project directory at {self.project_dir}")
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
self.logger.error(f"Failed to initialize project directory: {e}")
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
def _find_project_root(self) -> Optional[Path]:
|
|
115
|
+
"""Find project root by looking for .git or other project markers."""
|
|
116
|
+
current = Path.cwd()
|
|
117
|
+
while current != current.parent:
|
|
118
|
+
if (current / ".git").exists():
|
|
119
|
+
return current
|
|
120
|
+
if (current / "pyproject.toml").exists():
|
|
121
|
+
return current
|
|
122
|
+
if (current / "setup.py").exists():
|
|
123
|
+
return current
|
|
124
|
+
current = current.parent
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def _create_default_config(self, config_file: Path):
|
|
128
|
+
"""Create default user configuration."""
|
|
129
|
+
default_config = {
|
|
130
|
+
"version": "1.0",
|
|
131
|
+
"hooks": {
|
|
132
|
+
"enabled": True,
|
|
133
|
+
"port_range": [8080, 8099]
|
|
134
|
+
},
|
|
135
|
+
"logging": {
|
|
136
|
+
"level": "INFO",
|
|
137
|
+
"max_size_mb": 100,
|
|
138
|
+
"retention_days": 30
|
|
139
|
+
},
|
|
140
|
+
"agents": {
|
|
141
|
+
"auto_discover": True,
|
|
142
|
+
"precedence": ["project", "user", "system"]
|
|
143
|
+
},
|
|
144
|
+
"orchestration": {
|
|
145
|
+
"default_mode": "subprocess",
|
|
146
|
+
"enable_todo_hijacking": False
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
with open(config_file, 'w') as f:
|
|
151
|
+
json.dump(default_config, f, indent=2)
|
|
152
|
+
|
|
153
|
+
def _create_project_config(self, config_file: Path):
|
|
154
|
+
"""Create default project configuration."""
|
|
155
|
+
project_config = {
|
|
156
|
+
"version": "1.0",
|
|
157
|
+
"project_name": Path.cwd().name,
|
|
158
|
+
"agents": {
|
|
159
|
+
"enabled": True
|
|
160
|
+
},
|
|
161
|
+
"tickets": {
|
|
162
|
+
"auto_create": True,
|
|
163
|
+
"prefix": "TSK"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
with open(config_file, 'w') as f:
|
|
168
|
+
json.dump(project_config, f, indent=2)
|
|
169
|
+
|
|
170
|
+
def _copy_agent_templates(self):
|
|
171
|
+
"""Copy agent templates to user directory."""
|
|
172
|
+
# Get the package directory
|
|
173
|
+
package_dir = Path(__file__).parent
|
|
174
|
+
templates_src = package_dir / "agents" / "templates"
|
|
175
|
+
templates_dst = self.user_dir / "templates"
|
|
176
|
+
|
|
177
|
+
if templates_src.exists():
|
|
178
|
+
for template_file in templates_src.glob("*.md"):
|
|
179
|
+
dst_file = templates_dst / template_file.name
|
|
180
|
+
if not dst_file.exists():
|
|
181
|
+
shutil.copy2(template_file, dst_file)
|
|
182
|
+
|
|
183
|
+
def validate_dependencies(self) -> Dict[str, bool]:
|
|
184
|
+
"""Validate that all required dependencies are available."""
|
|
185
|
+
dependencies = {}
|
|
186
|
+
|
|
187
|
+
# Check Python version
|
|
188
|
+
import sys
|
|
189
|
+
dependencies['python'] = sys.version_info >= (3, 8)
|
|
190
|
+
|
|
191
|
+
# Check Claude CLI
|
|
192
|
+
dependencies['claude_cli'] = shutil.which("claude") is not None
|
|
193
|
+
|
|
194
|
+
# Check required Python packages
|
|
195
|
+
required_packages = [
|
|
196
|
+
'ai_trackdown_pytools',
|
|
197
|
+
'yaml',
|
|
198
|
+
'dotenv',
|
|
199
|
+
'rich',
|
|
200
|
+
'click',
|
|
201
|
+
'pexpect',
|
|
202
|
+
'psutil',
|
|
203
|
+
'requests',
|
|
204
|
+
'flask',
|
|
205
|
+
'watchdog',
|
|
206
|
+
'tree_sitter'
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
for package in required_packages:
|
|
210
|
+
try:
|
|
211
|
+
__import__(package)
|
|
212
|
+
dependencies[package] = True
|
|
213
|
+
except ImportError:
|
|
214
|
+
dependencies[package] = False
|
|
215
|
+
|
|
216
|
+
return dependencies
|
|
217
|
+
|
|
218
|
+
def ensure_initialized(self) -> bool:
|
|
219
|
+
"""Ensure both user and project directories are initialized."""
|
|
220
|
+
user_ok = self.initialize_user_directory()
|
|
221
|
+
project_ok = self.initialize_project_directory()
|
|
222
|
+
return user_ok and project_ok
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def ensure_directories():
|
|
226
|
+
"""Convenience function to ensure directories are initialized."""
|
|
227
|
+
initializer = ProjectInitializer()
|
|
228
|
+
return initializer.ensure_initialized()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def validate_installation():
|
|
232
|
+
"""Validate that claude-mpm is properly installed."""
|
|
233
|
+
initializer = ProjectInitializer()
|
|
234
|
+
deps = initializer.validate_dependencies()
|
|
235
|
+
|
|
236
|
+
all_ok = all(deps.values())
|
|
237
|
+
|
|
238
|
+
if not all_ok:
|
|
239
|
+
print("❌ Missing dependencies:")
|
|
240
|
+
for dep, status in deps.items():
|
|
241
|
+
if not status:
|
|
242
|
+
print(f" - {dep}")
|
|
243
|
+
else:
|
|
244
|
+
print("✅ All dependencies are installed")
|
|
245
|
+
|
|
246
|
+
return all_ok
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Subprocess Orchestration Design
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
Claude's `--print` mode times out when generating code or using tools, making subprocess orchestration in non-interactive mode impractical.
|
|
5
|
+
|
|
6
|
+
## Findings
|
|
7
|
+
|
|
8
|
+
### Interactive Mode (Working)
|
|
9
|
+
- Claude uses built-in Task tool
|
|
10
|
+
- Creates real subprocesses with ~11.4k tokens each
|
|
11
|
+
- Runs in parallel with independent timing
|
|
12
|
+
- Each subprocess gets framework context
|
|
13
|
+
|
|
14
|
+
### Non-Interactive Mode Issues
|
|
15
|
+
- `claude --print` works for simple queries (e.g., "What is 2+2?" in ~4s)
|
|
16
|
+
- Times out for any code generation or complex tasks
|
|
17
|
+
- Debug shows Claude is working but tool usage adds overhead
|
|
18
|
+
- Requires `--dangerously-skip-permissions` flag to run
|
|
19
|
+
|
|
20
|
+
## Alternative Approaches
|
|
21
|
+
|
|
22
|
+
### 1. Use Claude's Conversation API
|
|
23
|
+
Instead of `--print`, use conversation management:
|
|
24
|
+
```bash
|
|
25
|
+
# Start conversation
|
|
26
|
+
claude --model opus -c "conversation_id" < prompt.txt
|
|
27
|
+
|
|
28
|
+
# Continue conversation
|
|
29
|
+
claude --continue conversation_id < next_prompt.txt
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Use Interactive Mode with Expect
|
|
33
|
+
Use expect/pexpect to control interactive Claude sessions programmatically.
|
|
34
|
+
|
|
35
|
+
### 3. Mock Subprocess Mode
|
|
36
|
+
For testing/development:
|
|
37
|
+
- Detect delegations in PM response
|
|
38
|
+
- Show subprocess-like output
|
|
39
|
+
- But don't actually create subprocesses
|
|
40
|
+
|
|
41
|
+
### 4. Direct API Integration
|
|
42
|
+
Skip CLI entirely and use Claude's API directly (if available).
|
|
43
|
+
|
|
44
|
+
## Implementation Status
|
|
45
|
+
|
|
46
|
+
### Completed
|
|
47
|
+
1. ✅ SubprocessOrchestrator class with full functionality
|
|
48
|
+
2. ✅ Delegation detection for multiple formats
|
|
49
|
+
3. ✅ Parallel subprocess execution framework
|
|
50
|
+
4. ✅ Agent-specific prompt generation
|
|
51
|
+
5. ✅ CLI integration via `--subprocess` flag
|
|
52
|
+
6. ✅ Fixed command flags for permissions
|
|
53
|
+
|
|
54
|
+
### Current Status
|
|
55
|
+
- Implementation is complete but blocked by Claude CLI limitations
|
|
56
|
+
- Use interactive mode for real subprocess orchestration
|
|
57
|
+
- Keep implementation for future when Claude print mode improves
|
|
58
|
+
|
|
59
|
+
## Delegation Detection Patterns
|
|
60
|
+
|
|
61
|
+
The PM uses these formats:
|
|
62
|
+
- `**Engineer Agent**: Create a function...`
|
|
63
|
+
- `**QA**: Write tests for...`
|
|
64
|
+
- `I'll delegate this to the Engineer agent...`
|
|
65
|
+
|
|
66
|
+
We can parse these and show subprocess-style output even without real subprocesses.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Direct orchestrator that runs Claude with minimal intervention."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
import logging
|
|
8
|
+
import tempfile
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from ..core.logger import get_logger, setup_logging
|
|
12
|
+
from ..utils.subprocess_runner import SubprocessRunner
|
|
13
|
+
# TicketExtractor removed from project
|
|
14
|
+
from ..core.framework_loader import FrameworkLoader
|
|
15
|
+
from .agent_delegator import AgentDelegator
|
|
16
|
+
except ImportError:
|
|
17
|
+
from core.logger import get_logger, setup_logging
|
|
18
|
+
from utils.subprocess_runner import SubprocessRunner
|
|
19
|
+
# TicketExtractor removed from project
|
|
20
|
+
from core.framework_loader import FrameworkLoader
|
|
21
|
+
from orchestration.agent_delegator import AgentDelegator
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DirectOrchestrator:
|
|
25
|
+
"""Orchestrator that runs Claude directly with framework injection via file."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
framework_path: Optional[Path] = None,
|
|
30
|
+
agents_dir: Optional[Path] = None,
|
|
31
|
+
log_level: str = "OFF",
|
|
32
|
+
log_dir: Optional[Path] = None,
|
|
33
|
+
):
|
|
34
|
+
"""Initialize the orchestrator."""
|
|
35
|
+
self.log_level = log_level
|
|
36
|
+
self.log_dir = log_dir or (Path.home() / ".claude-mpm" / "logs")
|
|
37
|
+
|
|
38
|
+
# Set up logging
|
|
39
|
+
if log_level != "OFF":
|
|
40
|
+
self.logger = setup_logging(level=log_level, log_dir=log_dir)
|
|
41
|
+
self.logger.info(f"Initializing Direct Orchestrator (log_level={log_level})")
|
|
42
|
+
else:
|
|
43
|
+
# Minimal logger
|
|
44
|
+
self.logger = get_logger("direct_orchestrator")
|
|
45
|
+
self.logger.setLevel(logging.WARNING)
|
|
46
|
+
|
|
47
|
+
# Components
|
|
48
|
+
self.framework_loader = FrameworkLoader(framework_path, agents_dir)
|
|
49
|
+
# TicketExtractor removed from project
|
|
50
|
+
self.agent_delegator = AgentDelegator(self.framework_loader.agent_registry)
|
|
51
|
+
|
|
52
|
+
# State
|
|
53
|
+
self.session_start = datetime.now()
|
|
54
|
+
# Ticket creation removed from project
|
|
55
|
+
|
|
56
|
+
# Initialize subprocess runner
|
|
57
|
+
self.subprocess_runner = SubprocessRunner(logger=self.logger)
|
|
58
|
+
|
|
59
|
+
def run_interactive(self):
|
|
60
|
+
"""Run an interactive session by launching Claude directly."""
|
|
61
|
+
print("Claude MPM Interactive Session")
|
|
62
|
+
print("Framework will be injected on first interaction")
|
|
63
|
+
print("-" * 50)
|
|
64
|
+
|
|
65
|
+
# Get framework instructions
|
|
66
|
+
framework = self.framework_loader.get_framework_instructions()
|
|
67
|
+
|
|
68
|
+
# Save framework to a temporary file
|
|
69
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
70
|
+
f.write(framework)
|
|
71
|
+
f.write("\n\nNOTE: This is the claude-mpm framework. Please acknowledge you've received these instructions and then we can begin our session.\n")
|
|
72
|
+
framework_file = f.name
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# Log framework injection
|
|
76
|
+
if self.log_level != "OFF":
|
|
77
|
+
self.logger.info(f"Framework saved to temporary file: {framework_file}")
|
|
78
|
+
|
|
79
|
+
# Also save to prompts directory
|
|
80
|
+
prompt_path = Path.home() / ".claude-mpm" / "prompts"
|
|
81
|
+
prompt_path.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
prompt_file = prompt_path / f"prompt_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
|
83
|
+
prompt_file.write_text(framework)
|
|
84
|
+
self.logger.info(f"Framework also saved to: {prompt_file}")
|
|
85
|
+
|
|
86
|
+
# Read the framework content
|
|
87
|
+
with open(framework_file, 'r') as f:
|
|
88
|
+
framework_content = f.read()
|
|
89
|
+
|
|
90
|
+
# Generate a unique session ID
|
|
91
|
+
import uuid
|
|
92
|
+
session_id = str(uuid.uuid4())
|
|
93
|
+
|
|
94
|
+
# Build command to start Claude with print mode and session ID
|
|
95
|
+
cmd = [
|
|
96
|
+
"claude",
|
|
97
|
+
"--model", "opus",
|
|
98
|
+
"--dangerously-skip-permissions",
|
|
99
|
+
"--session-id", session_id,
|
|
100
|
+
"--print", # Print mode
|
|
101
|
+
framework_content
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
self.logger.info(f"Starting Claude with framework injection (session: {session_id})")
|
|
105
|
+
|
|
106
|
+
# Run Claude with framework
|
|
107
|
+
print("\nInjecting framework instructions...")
|
|
108
|
+
result = self.subprocess_runner.run(cmd)
|
|
109
|
+
|
|
110
|
+
if result.success:
|
|
111
|
+
print("\nFramework injected. Claude's response:")
|
|
112
|
+
print("-" * 50)
|
|
113
|
+
print(result.stdout)
|
|
114
|
+
print("-" * 50)
|
|
115
|
+
|
|
116
|
+
# Debug: show stderr if logging is enabled
|
|
117
|
+
if self.log_level != "OFF" and result.stderr:
|
|
118
|
+
self.logger.debug(f"Claude stderr: {result.stderr}")
|
|
119
|
+
|
|
120
|
+
# Check if we can find the conversation file
|
|
121
|
+
# Parse stderr for conversation file location
|
|
122
|
+
conversation_file = None
|
|
123
|
+
if result.stderr:
|
|
124
|
+
import re
|
|
125
|
+
# Look for patterns like "Conversation saved to: /path/to/file"
|
|
126
|
+
match = re.search(r'(?:conversation saved to|saved to)[:\s]+([^\s]+)', result.stderr, re.I)
|
|
127
|
+
if match:
|
|
128
|
+
conversation_file = match.group(1).strip()
|
|
129
|
+
self.logger.info(f"Found conversation file: {conversation_file}")
|
|
130
|
+
|
|
131
|
+
# Now start interactive Claude session with same session ID
|
|
132
|
+
print("\nStarting interactive session...")
|
|
133
|
+
interactive_cmd = [
|
|
134
|
+
"claude",
|
|
135
|
+
"--model", "opus",
|
|
136
|
+
"--dangerously-skip-permissions"
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
# Try to continue the conversation
|
|
140
|
+
if conversation_file and Path(conversation_file).exists():
|
|
141
|
+
interactive_cmd.extend(["--continue", conversation_file])
|
|
142
|
+
print(f"Continuing conversation from: {conversation_file}")
|
|
143
|
+
elif session_id:
|
|
144
|
+
interactive_cmd.extend(["--session-id", session_id])
|
|
145
|
+
print(f"Using session ID: {session_id}")
|
|
146
|
+
|
|
147
|
+
# Run Claude interactively
|
|
148
|
+
self.subprocess_runner.run(interactive_cmd)
|
|
149
|
+
else:
|
|
150
|
+
print(f"Error injecting framework: {result.stderr}")
|
|
151
|
+
|
|
152
|
+
self.logger.info(f"Claude exited with code: {result.returncode}")
|
|
153
|
+
|
|
154
|
+
finally:
|
|
155
|
+
# Clean up temporary file
|
|
156
|
+
try:
|
|
157
|
+
os.unlink(framework_file)
|
|
158
|
+
except:
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
# Ticket creation removed from project
|
|
162
|
+
|
|
163
|
+
# _create_tickets method removed - TicketExtractor functionality removed from project
|
|
164
|
+
|
|
165
|
+
def run_non_interactive(self, user_input: str):
|
|
166
|
+
"""Run a non-interactive session using print mode."""
|
|
167
|
+
try:
|
|
168
|
+
# Prepare message with framework
|
|
169
|
+
framework = self.framework_loader.get_framework_instructions()
|
|
170
|
+
full_message = framework + "\n\nUser: " + user_input
|
|
171
|
+
|
|
172
|
+
# Build command
|
|
173
|
+
cmd = [
|
|
174
|
+
"claude",
|
|
175
|
+
"--model", "opus",
|
|
176
|
+
"--dangerously-skip-permissions",
|
|
177
|
+
"--print", # Print mode
|
|
178
|
+
full_message
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
# Run Claude
|
|
182
|
+
result = self.subprocess_runner.run(cmd)
|
|
183
|
+
|
|
184
|
+
if result.success:
|
|
185
|
+
print(result.stdout)
|
|
186
|
+
|
|
187
|
+
# Ticket extraction removed from project
|
|
188
|
+
else:
|
|
189
|
+
print(f"Error: {result.stderr}")
|
|
190
|
+
|
|
191
|
+
# Ticket creation removed from project
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
print(f"Error: {e}")
|
|
195
|
+
self.logger.error(f"Non-interactive error: {e}")
|