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.

Files changed (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,511 @@
1
+ """Simplified Claude runner replacing the complex orchestrator system."""
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ import time
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ try:
13
+ from claude_mpm.services.agent_deployment import AgentDeploymentService
14
+ from claude_mpm.services.ticket_manager import TicketManager
15
+ from claude_mpm.core.logger import get_logger, get_project_logger, ProjectLogger
16
+ except ImportError:
17
+ from claude_mpm.services.agent_deployment import AgentDeploymentService
18
+ from claude_mpm.services.ticket_manager import TicketManager
19
+ from claude_mpm.core.logger import get_logger, get_project_logger, ProjectLogger
20
+
21
+
22
+ class SimpleClaudeRunner:
23
+ """
24
+ Simplified Claude runner that replaces the entire orchestrator system.
25
+
26
+ This does exactly what we need:
27
+ 1. Deploy native agents to .claude/agents/
28
+ 2. Run Claude CLI with basic subprocess calls
29
+ 3. Extract tickets if needed
30
+ 4. Handle both interactive and non-interactive modes
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ enable_tickets: bool = True,
36
+ log_level: str = "OFF",
37
+ claude_args: Optional[list] = None
38
+ ):
39
+ """Initialize the simple runner."""
40
+ self.enable_tickets = enable_tickets
41
+ self.log_level = log_level
42
+ self.logger = get_logger("simple_runner")
43
+ self.claude_args = claude_args or []
44
+
45
+ # Initialize project logger for session logging
46
+ self.project_logger = None
47
+ if log_level != "OFF":
48
+ try:
49
+ self.project_logger = get_project_logger(log_level)
50
+ self.project_logger.log_system(
51
+ "Initializing SimpleClaudeRunner",
52
+ level="INFO",
53
+ component="runner"
54
+ )
55
+ except Exception as e:
56
+ self.logger.warning(f"Failed to initialize project logger: {e}")
57
+
58
+ # Initialize services
59
+ self.deployment_service = AgentDeploymentService()
60
+ if enable_tickets:
61
+ try:
62
+ self.ticket_manager = TicketManager()
63
+ except (ImportError, TypeError, Exception) as e:
64
+ self.logger.warning(f"Ticket manager not available: {e}")
65
+ self.ticket_manager = None
66
+ self.enable_tickets = False
67
+
68
+ # Load system instructions
69
+ self.system_instructions = self._load_system_instructions()
70
+
71
+ # Track if we need to create session logs
72
+ self.session_log_file = None
73
+ if self.project_logger and log_level != "OFF":
74
+ try:
75
+ # Create a system.jsonl file in the session directory
76
+ self.session_log_file = self.project_logger.session_dir / "system.jsonl"
77
+ self._log_session_event({
78
+ "event": "session_start",
79
+ "runner": "SimpleClaudeRunner",
80
+ "enable_tickets": enable_tickets,
81
+ "log_level": log_level
82
+ })
83
+ except Exception as e:
84
+ self.logger.debug(f"Failed to create session log file: {e}")
85
+
86
+ def setup_agents(self) -> bool:
87
+ """Deploy native agents to .claude/agents/."""
88
+ try:
89
+ if self.project_logger:
90
+ self.project_logger.log_system(
91
+ "Starting agent deployment",
92
+ level="INFO",
93
+ component="deployment"
94
+ )
95
+
96
+ results = self.deployment_service.deploy_agents()
97
+
98
+ if results["deployed"] or results.get("updated", []):
99
+ deployed_count = len(results['deployed'])
100
+ updated_count = len(results.get('updated', []))
101
+
102
+ if deployed_count > 0:
103
+ print(f"✓ Deployed {deployed_count} native agents")
104
+ if updated_count > 0:
105
+ print(f"✓ Updated {updated_count} agents")
106
+
107
+ if self.project_logger:
108
+ self.project_logger.log_system(
109
+ f"Agent deployment successful: {deployed_count} deployed, {updated_count} updated",
110
+ level="INFO",
111
+ component="deployment"
112
+ )
113
+
114
+ # Set Claude environment
115
+ self.deployment_service.set_claude_environment()
116
+ return True
117
+ else:
118
+ self.logger.info("All agents already up to date")
119
+ if self.project_logger:
120
+ self.project_logger.log_system(
121
+ "All agents already up to date",
122
+ level="INFO",
123
+ component="deployment"
124
+ )
125
+ return True
126
+
127
+ except Exception as e:
128
+ self.logger.error(f"Agent deployment failed: {e}")
129
+ print(f"⚠️ Agent deployment failed: {e}")
130
+ if self.project_logger:
131
+ self.project_logger.log_system(
132
+ f"Agent deployment failed: {e}",
133
+ level="ERROR",
134
+ component="deployment"
135
+ )
136
+ return False
137
+
138
+ def run_interactive(self, initial_context: Optional[str] = None):
139
+ """Run Claude in interactive mode."""
140
+ # Get version
141
+ try:
142
+ from claude_mpm import __version__
143
+ version_str = f"v{__version__}"
144
+ except:
145
+ version_str = "v0.0.0"
146
+
147
+ # Print styled welcome box
148
+ print("\033[32m╭───────────────────────────────────────────────────╮\033[0m")
149
+ print("\033[32m│\033[0m ✻ Claude MPM - Interactive Session \033[32m│\033[0m")
150
+ print(f"\033[32m│\033[0m Version {version_str:<40}\033[32m│\033[0m")
151
+ print("\033[32m│ │\033[0m")
152
+ print("\033[32m│\033[0m Type '/agents' to see available agents \033[32m│\033[0m")
153
+ print("\033[32m╰───────────────────────────────────────────────────╯\033[0m")
154
+ print("") # Add blank line after box
155
+
156
+ if self.project_logger:
157
+ self.project_logger.log_system(
158
+ "Starting interactive session",
159
+ level="INFO",
160
+ component="session"
161
+ )
162
+
163
+ # Setup agents
164
+ if not self.setup_agents():
165
+ print("Continuing without native agents...")
166
+
167
+ # Build command with system instructions
168
+ cmd = [
169
+ "claude",
170
+ "--model", "opus",
171
+ "--dangerously-skip-permissions"
172
+ ]
173
+
174
+ # Add any custom Claude arguments
175
+ if self.claude_args:
176
+ cmd.extend(self.claude_args)
177
+
178
+ # Add system instructions if available
179
+ system_prompt = self._create_system_prompt()
180
+ if system_prompt and system_prompt != create_simple_context():
181
+ cmd.extend(["--append-system-prompt", system_prompt])
182
+
183
+ # Run interactive Claude directly
184
+ try:
185
+ # Use execvp to replace the current process with Claude
186
+ # This should avoid any subprocess issues
187
+
188
+ # Clean environment
189
+ clean_env = os.environ.copy()
190
+ claude_vars_to_remove = [
191
+ 'CLAUDE_CODE_ENTRYPOINT', 'CLAUDECODE', 'CLAUDE_CONFIG_DIR',
192
+ 'CLAUDE_MAX_PARALLEL_SUBAGENTS', 'CLAUDE_TIMEOUT'
193
+ ]
194
+ for var in claude_vars_to_remove:
195
+ clean_env.pop(var, None)
196
+
197
+ print("Launching Claude...")
198
+
199
+ if self.project_logger:
200
+ self.project_logger.log_system(
201
+ "Launching Claude interactive mode",
202
+ level="INFO",
203
+ component="session"
204
+ )
205
+ self._log_session_event({
206
+ "event": "launching_claude_interactive",
207
+ "command": " ".join(cmd)
208
+ })
209
+
210
+ # Replace current process with Claude
211
+ os.execvpe(cmd[0], cmd, clean_env)
212
+
213
+ except Exception as e:
214
+ print(f"Failed to launch Claude: {e}")
215
+ if self.project_logger:
216
+ self.project_logger.log_system(
217
+ f"Failed to launch Claude: {e}",
218
+ level="ERROR",
219
+ component="session"
220
+ )
221
+ self._log_session_event({
222
+ "event": "interactive_launch_failed",
223
+ "error": str(e),
224
+ "exception_type": type(e).__name__
225
+ })
226
+ # Fallback to subprocess
227
+ try:
228
+ subprocess.run(cmd, stdin=None, stdout=None, stderr=None)
229
+ if self.project_logger:
230
+ self.project_logger.log_system(
231
+ "Interactive session completed (subprocess fallback)",
232
+ level="INFO",
233
+ component="session"
234
+ )
235
+ self._log_session_event({
236
+ "event": "interactive_session_complete",
237
+ "fallback": True
238
+ })
239
+ except Exception as fallback_error:
240
+ print(f"Fallback also failed: {fallback_error}")
241
+ if self.project_logger:
242
+ self.project_logger.log_system(
243
+ f"Fallback launch failed: {fallback_error}",
244
+ level="ERROR",
245
+ component="session"
246
+ )
247
+ self._log_session_event({
248
+ "event": "interactive_fallback_failed",
249
+ "error": str(fallback_error),
250
+ "exception_type": type(fallback_error).__name__
251
+ })
252
+
253
+ def run_oneshot(self, prompt: str, context: Optional[str] = None) -> bool:
254
+ """Run Claude with a single prompt and return success status."""
255
+ start_time = time.time()
256
+
257
+ if self.project_logger:
258
+ self.project_logger.log_system(
259
+ f"Starting non-interactive session with prompt: {prompt[:100]}",
260
+ level="INFO",
261
+ component="session"
262
+ )
263
+
264
+ # Setup agents
265
+ if not self.setup_agents():
266
+ print("Continuing without native agents...")
267
+
268
+ # Combine context and prompt
269
+ full_prompt = prompt
270
+ if context:
271
+ full_prompt = f"{context}\n\n{prompt}"
272
+
273
+ # Build command with system instructions
274
+ cmd = [
275
+ "claude",
276
+ "--model", "opus",
277
+ "--dangerously-skip-permissions"
278
+ ]
279
+
280
+ # Add any custom Claude arguments
281
+ if self.claude_args:
282
+ cmd.extend(self.claude_args)
283
+
284
+ # Add print and prompt
285
+ cmd.extend(["--print", full_prompt])
286
+
287
+ # Add system instructions if available
288
+ system_prompt = self._create_system_prompt()
289
+ if system_prompt and system_prompt != create_simple_context():
290
+ # Insert system prompt before the user prompt
291
+ cmd.insert(-2, "--append-system-prompt")
292
+ cmd.insert(-2, system_prompt)
293
+
294
+ try:
295
+ # Run Claude
296
+ if self.project_logger:
297
+ self.project_logger.log_system(
298
+ "Executing Claude subprocess",
299
+ level="INFO",
300
+ component="session"
301
+ )
302
+
303
+ result = subprocess.run(cmd, capture_output=True, text=True)
304
+ execution_time = time.time() - start_time
305
+
306
+ if result.returncode == 0:
307
+ response = result.stdout.strip()
308
+ print(response)
309
+
310
+ if self.project_logger:
311
+ # Log successful completion
312
+ self.project_logger.log_system(
313
+ f"Non-interactive session completed successfully in {execution_time:.2f}s",
314
+ level="INFO",
315
+ component="session"
316
+ )
317
+
318
+ # Log session event
319
+ self._log_session_event({
320
+ "event": "session_complete",
321
+ "success": True,
322
+ "execution_time": execution_time,
323
+ "response_length": len(response)
324
+ })
325
+
326
+ # Log agent invocation if we detect delegation patterns
327
+ if self._contains_delegation(response):
328
+ self.project_logger.log_system(
329
+ "Detected potential agent delegation in response",
330
+ level="INFO",
331
+ component="delegation"
332
+ )
333
+ self._log_session_event({
334
+ "event": "delegation_detected",
335
+ "prompt": prompt[:200],
336
+ "indicators": [p for p in ["Task(", "subagent_type=", "engineer agent", "qa agent"]
337
+ if p.lower() in response.lower()]
338
+ })
339
+
340
+ # Extract tickets if enabled
341
+ if self.enable_tickets and self.ticket_manager and response:
342
+ self._extract_tickets(response)
343
+
344
+ return True
345
+ else:
346
+ error_msg = result.stderr or "Unknown error"
347
+ print(f"Error: {error_msg}")
348
+
349
+ if self.project_logger:
350
+ self.project_logger.log_system(
351
+ f"Non-interactive session failed: {error_msg}",
352
+ level="ERROR",
353
+ component="session"
354
+ )
355
+ self._log_session_event({
356
+ "event": "session_failed",
357
+ "success": False,
358
+ "error": error_msg,
359
+ "return_code": result.returncode
360
+ })
361
+
362
+ return False
363
+
364
+ except Exception as e:
365
+ print(f"Error: {e}")
366
+
367
+ if self.project_logger:
368
+ self.project_logger.log_system(
369
+ f"Exception during non-interactive session: {e}",
370
+ level="ERROR",
371
+ component="session"
372
+ )
373
+ self._log_session_event({
374
+ "event": "session_exception",
375
+ "success": False,
376
+ "exception": str(e),
377
+ "exception_type": type(e).__name__
378
+ })
379
+
380
+ return False
381
+ finally:
382
+ # Ensure logs are flushed
383
+ if self.project_logger:
384
+ try:
385
+ # Log session summary
386
+ summary = self.project_logger.get_session_summary()
387
+ self.project_logger.log_system(
388
+ f"Session {summary['session_id']} completed",
389
+ level="INFO",
390
+ component="session"
391
+ )
392
+ except Exception as e:
393
+ self.logger.debug(f"Failed to log session summary: {e}")
394
+
395
+ def _extract_tickets(self, text: str):
396
+ """Extract tickets from Claude's response."""
397
+ if not self.ticket_manager:
398
+ return
399
+
400
+ try:
401
+ # Use the ticket manager's extraction logic if available
402
+ if hasattr(self.ticket_manager, 'extract_tickets_from_text'):
403
+ tickets = self.ticket_manager.extract_tickets_from_text(text)
404
+ if tickets:
405
+ print(f"\n📋 Extracted {len(tickets)} tickets")
406
+ for ticket in tickets[:3]: # Show first 3
407
+ print(f" - [{ticket.get('id', 'N/A')}] {ticket.get('title', 'No title')}")
408
+ if len(tickets) > 3:
409
+ print(f" ... and {len(tickets) - 3} more")
410
+ else:
411
+ self.logger.debug("Ticket extraction method not available")
412
+ except Exception as e:
413
+ self.logger.debug(f"Ticket extraction failed: {e}")
414
+
415
+ def _load_system_instructions(self) -> Optional[str]:
416
+ """Load system instructions from agents/INSTRUCTIONS.md."""
417
+ try:
418
+ # Find the INSTRUCTIONS.md file
419
+ module_path = Path(__file__).parent.parent
420
+ instructions_path = module_path / "agents" / "INSTRUCTIONS.md"
421
+
422
+ if not instructions_path.exists():
423
+ self.logger.warning(f"System instructions not found: {instructions_path}")
424
+ return None
425
+
426
+ instructions = instructions_path.read_text()
427
+ self.logger.info("Loaded PM framework system instructions")
428
+ return instructions
429
+
430
+ except Exception as e:
431
+ self.logger.error(f"Failed to load system instructions: {e}")
432
+ return None
433
+
434
+ def _create_system_prompt(self) -> str:
435
+ """Create the complete system prompt including instructions."""
436
+ if self.system_instructions:
437
+ return self.system_instructions
438
+ else:
439
+ # Fallback to basic context
440
+ return create_simple_context()
441
+
442
+ def _contains_delegation(self, text: str) -> bool:
443
+ """Check if text contains signs of agent delegation."""
444
+ # Look for common delegation patterns
445
+ delegation_patterns = [
446
+ "Task(",
447
+ "subagent_type=",
448
+ "delegating to",
449
+ "asking the",
450
+ "engineer agent",
451
+ "qa agent",
452
+ "documentation agent",
453
+ "research agent",
454
+ "security agent",
455
+ "ops agent",
456
+ "version_control agent",
457
+ "data_engineer agent"
458
+ ]
459
+
460
+ text_lower = text.lower()
461
+ return any(pattern.lower() in text_lower for pattern in delegation_patterns)
462
+
463
+ def _log_session_event(self, event_data: dict):
464
+ """Log an event to the session log file."""
465
+ if self.session_log_file:
466
+ try:
467
+ log_entry = {
468
+ "timestamp": datetime.now().isoformat(),
469
+ **event_data
470
+ }
471
+
472
+ with open(self.session_log_file, 'a') as f:
473
+ f.write(json.dumps(log_entry) + '\n')
474
+ except Exception as e:
475
+ self.logger.debug(f"Failed to log session event: {e}")
476
+
477
+
478
+ def create_simple_context() -> str:
479
+ """Create basic context for Claude."""
480
+ return """You are Claude Code running in Claude MPM (Multi-Agent Project Manager).
481
+
482
+ You have access to native subagents via the Task tool with subagent_type parameter:
483
+ - engineer: For coding, implementation, and technical tasks
484
+ - qa: For testing, validation, and quality assurance
485
+ - documentation: For docs, guides, and explanations
486
+ - research: For investigation and analysis
487
+ - security: For security-related tasks
488
+ - ops: For deployment and infrastructure
489
+ - version-control: For git and version management
490
+ - data-engineer: For data processing and APIs
491
+
492
+ Use these agents by calling: Task(description="task description", subagent_type="agent_name")
493
+
494
+ Work efficiently and delegate appropriately to subagents when needed."""
495
+
496
+
497
+ # Convenience functions for backward compatibility
498
+ def run_claude_interactive(context: Optional[str] = None):
499
+ """Run Claude interactively with optional context."""
500
+ runner = SimpleClaudeRunner()
501
+ if context is None:
502
+ context = create_simple_context()
503
+ runner.run_interactive(context)
504
+
505
+
506
+ def run_claude_oneshot(prompt: str, context: Optional[str] = None) -> bool:
507
+ """Run Claude with a single prompt."""
508
+ runner = SimpleClaudeRunner()
509
+ if context is None:
510
+ context = create_simple_context()
511
+ return runner.run_oneshot(prompt, context)
@@ -0,0 +1,173 @@
1
+ """Tool access control system for managing which tools agents can use."""
2
+
3
+ from typing import Dict, List, Set, Optional
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class ToolAccessControl:
10
+ """
11
+ Manages tool access permissions for different agent types.
12
+
13
+ This ensures that only authorized agents can access specific tools,
14
+ particularly restricting TodoWrite to the PM (parent process) only.
15
+ """
16
+
17
+ # Default tool sets for different contexts
18
+ PM_TOOLS = {
19
+ "Task", # For delegating to agents
20
+ "TodoWrite", # For tracking tasks (PM ONLY)
21
+ "WebSearch", # For understanding requirements
22
+ "WebFetch" # For fetching external resources
23
+ }
24
+
25
+ AGENT_TOOLS = {
26
+ "Read", # Read files
27
+ "Write", # Write files
28
+ "Edit", # Edit files
29
+ "MultiEdit", # Multiple edits
30
+ "Bash", # Execute commands
31
+ "Grep", # Search in files
32
+ "Glob", # File pattern matching
33
+ "LS", # List directory
34
+ "WebSearch", # Search the web
35
+ "WebFetch", # Fetch web content
36
+ "NotebookRead", # Read Jupyter notebooks
37
+ "NotebookEdit" # Edit Jupyter notebooks
38
+ }
39
+
40
+ # Tool restrictions by agent type
41
+ AGENT_RESTRICTIONS: Dict[str, Set[str]] = {
42
+ # PM has very limited tools - delegation only
43
+ "pm": PM_TOOLS,
44
+
45
+ # All other agents get standard tools WITHOUT TodoWrite
46
+ "engineer": AGENT_TOOLS,
47
+ "research": AGENT_TOOLS,
48
+ "qa": AGENT_TOOLS,
49
+ "security": AGENT_TOOLS,
50
+ "documentation": AGENT_TOOLS,
51
+ "ops": AGENT_TOOLS,
52
+ "version_control": AGENT_TOOLS,
53
+ "data_engineer": AGENT_TOOLS,
54
+ "architect": AGENT_TOOLS
55
+ }
56
+
57
+ def __init__(self):
58
+ """Initialize the tool access control system."""
59
+ self.custom_restrictions: Dict[str, Set[str]] = {}
60
+ logger.info("Tool access control system initialized")
61
+
62
+ def get_allowed_tools(self, agent_type: str, is_parent: bool = False) -> List[str]:
63
+ """
64
+ Get the list of allowed tools for an agent type.
65
+
66
+ Args:
67
+ agent_type: The type of agent (pm, engineer, research, etc.)
68
+ is_parent: Whether this is the parent process (PM)
69
+
70
+ Returns:
71
+ List of allowed tool names
72
+ """
73
+ # Normalize agent type
74
+ agent_type = agent_type.lower().replace(" ", "_").replace("-", "_")
75
+
76
+ # Parent process (PM) gets PM tools
77
+ if is_parent or agent_type == "pm":
78
+ allowed = self.PM_TOOLS.copy()
79
+ logger.debug(f"PM/Parent process allowed tools: {allowed}")
80
+ return sorted(list(allowed))
81
+
82
+ # Check custom restrictions first
83
+ if agent_type in self.custom_restrictions:
84
+ allowed = self.custom_restrictions[agent_type]
85
+ elif agent_type in self.AGENT_RESTRICTIONS:
86
+ allowed = self.AGENT_RESTRICTIONS[agent_type]
87
+ else:
88
+ # Default to agent tools for unknown types
89
+ logger.warning(f"Unknown agent type '{agent_type}', using default agent tools")
90
+ allowed = self.AGENT_TOOLS.copy()
91
+
92
+ # Ensure TodoWrite is NEVER in child agent tools
93
+ allowed = allowed - {"TodoWrite"}
94
+
95
+ logger.debug(f"Agent '{agent_type}' allowed tools: {allowed}")
96
+ return sorted(list(allowed))
97
+
98
+ def format_allowed_tools_arg(self, agent_type: str, is_parent: bool = False) -> str:
99
+ """
100
+ Format the allowed tools as a comma-separated string for --allowedTools argument.
101
+
102
+ Args:
103
+ agent_type: The type of agent
104
+ is_parent: Whether this is the parent process
105
+
106
+ Returns:
107
+ Comma-separated string of allowed tools
108
+ """
109
+ allowed_tools = self.get_allowed_tools(agent_type, is_parent)
110
+ return ",".join(allowed_tools)
111
+
112
+ def set_custom_restrictions(self, agent_type: str, allowed_tools: Set[str]):
113
+ """
114
+ Set custom tool restrictions for a specific agent type.
115
+
116
+ Args:
117
+ agent_type: The agent type to customize
118
+ allowed_tools: Set of allowed tool names
119
+ """
120
+ agent_type = agent_type.lower().replace(" ", "_").replace("-", "_")
121
+
122
+ # Ensure TodoWrite is not in custom restrictions for non-PM agents
123
+ if agent_type != "pm" and "TodoWrite" in allowed_tools:
124
+ logger.warning(f"Removing TodoWrite from custom restrictions for {agent_type}")
125
+ allowed_tools = allowed_tools - {"TodoWrite"}
126
+
127
+ self.custom_restrictions[agent_type] = allowed_tools
128
+ logger.info(f"Set custom tool restrictions for {agent_type}: {allowed_tools}")
129
+
130
+ def validate_tool_usage(self, agent_type: str, tool_name: str, is_parent: bool = False) -> bool:
131
+ """
132
+ Validate if an agent is allowed to use a specific tool.
133
+
134
+ Args:
135
+ agent_type: The type of agent
136
+ tool_name: The name of the tool
137
+ is_parent: Whether this is the parent process
138
+
139
+ Returns:
140
+ True if allowed, False otherwise
141
+ """
142
+ allowed_tools = self.get_allowed_tools(agent_type, is_parent)
143
+ is_allowed = tool_name in allowed_tools
144
+
145
+ if not is_allowed:
146
+ logger.warning(f"Agent '{agent_type}' attempted to use forbidden tool '{tool_name}'")
147
+
148
+ return is_allowed
149
+
150
+ def get_todo_guidance(self, agent_type: str) -> str:
151
+ """
152
+ Get guidance text for agents on how to handle TODOs.
153
+
154
+ Args:
155
+ agent_type: The type of agent
156
+
157
+ Returns:
158
+ Guidance text for handling TODOs
159
+ """
160
+ if agent_type.lower() == "pm":
161
+ return """You have access to the TodoWrite tool for tracking tasks. Always prefix todos with [Agent] to indicate delegation target."""
162
+ else:
163
+ return """You do NOT have access to TodoWrite. Instead, when you identify tasks that need tracking:
164
+ 1. Include them in your response with clear markers like "TODO:" or "TASK:"
165
+ 2. Format them as a structured list with priority and description
166
+ 3. The PM will extract and track these in the central todo list
167
+ 4. Example format:
168
+ TODO (High Priority): [Research] Analyze authentication patterns
169
+ TODO (Medium Priority): [QA] Write tests for new endpoints"""
170
+
171
+
172
+ # Global instance for easy access
173
+ tool_access_control = ToolAccessControl()