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,243 @@
|
|
|
1
|
+
# Claude MPM Hook System
|
|
2
|
+
|
|
3
|
+
The Claude MPM hook system provides extensibility points for customizing behavior at key stages of execution.
|
|
4
|
+
|
|
5
|
+
## Current Implementation (v0.5.0+)
|
|
6
|
+
|
|
7
|
+
As of version 0.5.0, the hook system uses JSON-RPC for all hook executions. The previous HTTP-based implementation is deprecated and will be removed in a future release.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
The hook system allows you to intercept and modify behavior at various points in the orchestration workflow:
|
|
12
|
+
|
|
13
|
+
- **Submit Hooks**: Process user prompts before orchestration
|
|
14
|
+
- **Pre-Delegation Hooks**: Filter/enhance context before delegating to agents
|
|
15
|
+
- **Post-Delegation Hooks**: Validate/process results from agents
|
|
16
|
+
- **Ticket Extraction Hooks**: Automatically extract and create tickets from conversations
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
22
|
+
│ Orchestrator │────▶│ JSON-RPC Client │────▶│ Hook Types │
|
|
23
|
+
│ │ │ │ ├─────────────────┤
|
|
24
|
+
│ - Process prompt│ │ - No server req. │ │ - SubmitHook │
|
|
25
|
+
│ - Delegate work │ │ - Direct exec │ │ - PreDelegation │
|
|
26
|
+
│ - Create tickets│ │ - Auto discovery │ │ - PostDelegation│
|
|
27
|
+
└─────────────────┘ └──────────────────┘ │ - TicketExtract │
|
|
28
|
+
└─────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Basic Client Usage
|
|
34
|
+
```python
|
|
35
|
+
from claude_mpm.hooks.json_rpc_hook_client import JSONRPCHookClient
|
|
36
|
+
|
|
37
|
+
# Create client
|
|
38
|
+
client = JSONRPCHookClient()
|
|
39
|
+
|
|
40
|
+
# Check system health
|
|
41
|
+
health = client.health_check()
|
|
42
|
+
print(f"Status: {health['status']}")
|
|
43
|
+
print(f"Hooks available: {health['hook_count']}")
|
|
44
|
+
|
|
45
|
+
# List available hooks
|
|
46
|
+
hooks = client.list_hooks()
|
|
47
|
+
for hook_type, hook_list in hooks.items():
|
|
48
|
+
print(f"{hook_type}: {len(hook_list)} hooks")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Executing Hooks
|
|
52
|
+
```python
|
|
53
|
+
# Execute submit hooks
|
|
54
|
+
results = client.execute_submit_hook(
|
|
55
|
+
prompt="URGENT: Fix the login bug",
|
|
56
|
+
user_id="user123"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Get modified data
|
|
60
|
+
modified_data = client.get_modified_data(results)
|
|
61
|
+
if modified_data.get('priority') == 'high':
|
|
62
|
+
print("High priority task detected!")
|
|
63
|
+
|
|
64
|
+
# Execute pre-delegation hooks
|
|
65
|
+
results = client.execute_pre_delegation_hook(
|
|
66
|
+
agent="engineer",
|
|
67
|
+
context={"task": "implement feature"}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Execute ticket extraction
|
|
71
|
+
results = client.execute_ticket_extraction_hook(
|
|
72
|
+
content="TODO: Add tests\nFIXME: Memory leak"
|
|
73
|
+
)
|
|
74
|
+
tickets = client.get_extracted_tickets(results)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Hook Types
|
|
78
|
+
|
|
79
|
+
### 1. Submit Hooks
|
|
80
|
+
Process user prompts before they're sent to the orchestrator:
|
|
81
|
+
```python
|
|
82
|
+
from claude_mpm.hooks.base_hook import SubmitHook, HookContext, HookResult
|
|
83
|
+
|
|
84
|
+
class TicketDetectionSubmitHook(SubmitHook):
|
|
85
|
+
name = "ticket_detection"
|
|
86
|
+
priority = 10
|
|
87
|
+
|
|
88
|
+
def execute(self, context: HookContext) -> HookResult:
|
|
89
|
+
prompt = context.data.get('prompt', '')
|
|
90
|
+
# Detect ticket references like TSK-001
|
|
91
|
+
tickets = self.ticket_pattern.findall(prompt)
|
|
92
|
+
return HookResult(
|
|
93
|
+
success=True,
|
|
94
|
+
data={'tickets': tickets},
|
|
95
|
+
modified=True
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Pre-Delegation Hooks
|
|
100
|
+
Modify agent context before delegation:
|
|
101
|
+
```python
|
|
102
|
+
from claude_mpm.hooks.base_hook import PreDelegationHook
|
|
103
|
+
|
|
104
|
+
class ContextFilterHook(PreDelegationHook):
|
|
105
|
+
name = "context_filter"
|
|
106
|
+
priority = 20
|
|
107
|
+
|
|
108
|
+
def execute(self, context: HookContext) -> HookResult:
|
|
109
|
+
# Filter sensitive information
|
|
110
|
+
filtered_context = self._filter_sensitive(context.data['context'])
|
|
111
|
+
return HookResult(
|
|
112
|
+
success=True,
|
|
113
|
+
data={'context': filtered_context},
|
|
114
|
+
modified=True
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3. Post-Delegation Hooks
|
|
119
|
+
Process agent results:
|
|
120
|
+
```python
|
|
121
|
+
from claude_mpm.hooks.base_hook import PostDelegationHook
|
|
122
|
+
|
|
123
|
+
class ResultValidatorHook(PostDelegationHook):
|
|
124
|
+
name = "result_validator"
|
|
125
|
+
priority = 30
|
|
126
|
+
|
|
127
|
+
def execute(self, context: HookContext) -> HookResult:
|
|
128
|
+
result = context.data.get('result', {})
|
|
129
|
+
# Validate result quality
|
|
130
|
+
issues = self._validate_result(result)
|
|
131
|
+
return HookResult(
|
|
132
|
+
success=True,
|
|
133
|
+
data={'validation_issues': issues},
|
|
134
|
+
modified=bool(issues)
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 4. Ticket Extraction Hooks
|
|
139
|
+
Extract actionable items from conversations:
|
|
140
|
+
```python
|
|
141
|
+
from claude_mpm.hooks.base_hook import TicketExtractionHook
|
|
142
|
+
|
|
143
|
+
class AutoTicketExtractionHook(TicketExtractionHook):
|
|
144
|
+
name = "auto_ticket_extraction"
|
|
145
|
+
priority = 40
|
|
146
|
+
|
|
147
|
+
def execute(self, context: HookContext) -> HookResult:
|
|
148
|
+
content = context.data.get('content', '')
|
|
149
|
+
# Extract TODO, FIXME, etc.
|
|
150
|
+
tickets = self._extract_tickets(content)
|
|
151
|
+
return HookResult(
|
|
152
|
+
success=True,
|
|
153
|
+
data={'tickets': tickets},
|
|
154
|
+
modified=True
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Creating Custom Hooks
|
|
159
|
+
|
|
160
|
+
### 1. Create Hook File
|
|
161
|
+
Create a new Python file in the `builtin/` directory:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# builtin/my_custom_hook.py
|
|
165
|
+
from claude_mpm.hooks.base_hook import SubmitHook, HookContext, HookResult
|
|
166
|
+
|
|
167
|
+
class MyCustomHook(SubmitHook):
|
|
168
|
+
name = "my_custom_hook"
|
|
169
|
+
priority = 25 # 0-100, lower executes first
|
|
170
|
+
|
|
171
|
+
def execute(self, context: HookContext) -> HookResult:
|
|
172
|
+
# Your hook logic here
|
|
173
|
+
prompt = context.data.get('prompt', '')
|
|
174
|
+
|
|
175
|
+
# Process prompt
|
|
176
|
+
processed = self._process(prompt)
|
|
177
|
+
|
|
178
|
+
return HookResult(
|
|
179
|
+
success=True,
|
|
180
|
+
data={'prompt': processed},
|
|
181
|
+
modified=True
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def _process(self, prompt: str) -> str:
|
|
185
|
+
# Your processing logic
|
|
186
|
+
return prompt.upper()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 2. Hook Discovery
|
|
190
|
+
Hooks are automatically discovered from the `builtin/` directory when the client is initialized. No manual registration required!
|
|
191
|
+
|
|
192
|
+
### 3. Hook Priority
|
|
193
|
+
Hooks execute in priority order (0-100, lower first):
|
|
194
|
+
- 0-20: Critical preprocessing (security, validation)
|
|
195
|
+
- 21-40: Data transformation
|
|
196
|
+
- 41-60: Enhancement and enrichment
|
|
197
|
+
- 61-80: Analytics and metrics
|
|
198
|
+
- 81-100: Low priority post-processing
|
|
199
|
+
|
|
200
|
+
## Migration from HTTP-based Hooks
|
|
201
|
+
|
|
202
|
+
If you're migrating from the old HTTP-based hook system, see `/docs/hook_system_migration_guide.md` for detailed instructions.
|
|
203
|
+
|
|
204
|
+
Key changes:
|
|
205
|
+
- No server startup required
|
|
206
|
+
- Import from `json_rpc_hook_client` instead of `hook_client`
|
|
207
|
+
- Automatic hook discovery from `builtin/` directory
|
|
208
|
+
- Better error handling and performance
|
|
209
|
+
|
|
210
|
+
## Best Practices
|
|
211
|
+
|
|
212
|
+
1. **Keep Hooks Fast**: Hooks run synchronously, so keep execution time minimal
|
|
213
|
+
2. **Handle Errors Gracefully**: Always return a HookResult, even on failure
|
|
214
|
+
3. **Use Appropriate Priority**: Consider hook dependencies when setting priority
|
|
215
|
+
4. **Validate Input**: Always validate context data before processing
|
|
216
|
+
5. **Log Important Events**: Use logging for debugging and monitoring
|
|
217
|
+
6. **Make Hooks Idempotent**: Hooks should produce same result if run multiple times
|
|
218
|
+
|
|
219
|
+
## Troubleshooting
|
|
220
|
+
|
|
221
|
+
### Hooks Not Discovered
|
|
222
|
+
- Verify hook file is in `builtin/` directory
|
|
223
|
+
- Check file has `.py` extension
|
|
224
|
+
- Ensure hook class inherits from correct base type
|
|
225
|
+
- Check for Python syntax errors
|
|
226
|
+
|
|
227
|
+
### Hook Execution Errors
|
|
228
|
+
- Enable debug logging to see detailed errors
|
|
229
|
+
- Check hook's execute method returns HookResult
|
|
230
|
+
- Verify context data structure matches expectations
|
|
231
|
+
|
|
232
|
+
### Performance Issues
|
|
233
|
+
- Check hook execution times in results
|
|
234
|
+
- Consider caching expensive operations
|
|
235
|
+
- Profile hooks with `cProfile` if needed
|
|
236
|
+
|
|
237
|
+
## Examples
|
|
238
|
+
|
|
239
|
+
See the `builtin/` directory for example implementations:
|
|
240
|
+
- `submit_hook_example.py`: Ticket and priority detection
|
|
241
|
+
- `pre_delegation_hook_example.py`: Context filtering and enhancement
|
|
242
|
+
- `post_delegation_hook_example.py`: Result validation and metrics
|
|
243
|
+
- `ticket_extraction_hook_example.py`: Automatic ticket extraction
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Base hook class and types for claude-mpm hook system."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Dict, List, Optional, Union
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HookType(Enum):
|
|
15
|
+
"""Types of hooks available in the system."""
|
|
16
|
+
SUBMIT = "submit" # Process user prompts
|
|
17
|
+
PRE_DELEGATION = "pre_delegation" # Filter context before delegation
|
|
18
|
+
POST_DELEGATION = "post_delegation" # Process results after delegation
|
|
19
|
+
TICKET_EXTRACTION = "ticket_extraction" # Extract and create tickets
|
|
20
|
+
CUSTOM = "custom" # User-defined hooks
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class HookContext:
|
|
25
|
+
"""Context passed to hooks for processing."""
|
|
26
|
+
hook_type: HookType
|
|
27
|
+
data: Dict[str, Any]
|
|
28
|
+
metadata: Dict[str, Any]
|
|
29
|
+
timestamp: datetime
|
|
30
|
+
session_id: Optional[str] = None
|
|
31
|
+
user_id: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
def __post_init__(self):
|
|
34
|
+
"""Ensure timestamp is set."""
|
|
35
|
+
if not hasattr(self, 'timestamp') or self.timestamp is None:
|
|
36
|
+
self.timestamp = datetime.now()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class HookResult:
|
|
41
|
+
"""Result returned from hook execution."""
|
|
42
|
+
success: bool
|
|
43
|
+
data: Optional[Dict[str, Any]] = None
|
|
44
|
+
error: Optional[str] = None
|
|
45
|
+
modified: bool = False
|
|
46
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
47
|
+
execution_time_ms: Optional[float] = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BaseHook(ABC):
|
|
51
|
+
"""Base class for all hooks."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, name: str, priority: int = 50):
|
|
54
|
+
"""Initialize hook with name and priority.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: Unique name for the hook
|
|
58
|
+
priority: Execution priority (0-100, lower executes first)
|
|
59
|
+
"""
|
|
60
|
+
self.name = name
|
|
61
|
+
self.priority = max(0, min(100, priority)) # Clamp to 0-100
|
|
62
|
+
self.enabled = True
|
|
63
|
+
self._async = False
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def execute(self, context: HookContext) -> HookResult:
|
|
67
|
+
"""Execute the hook with given context.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
context: Hook context containing data and metadata
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
HookResult with execution results
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
async def async_execute(self, context: HookContext) -> HookResult:
|
|
78
|
+
"""Async version of execute. Override for async hooks."""
|
|
79
|
+
# Default implementation calls sync execute in executor
|
|
80
|
+
loop = asyncio.get_event_loop()
|
|
81
|
+
return await loop.run_in_executor(None, self.execute, context)
|
|
82
|
+
|
|
83
|
+
def validate(self, context: HookContext) -> bool:
|
|
84
|
+
"""Validate if hook should run for given context.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
context: Hook context to validate
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
True if hook should execute, False otherwise
|
|
91
|
+
"""
|
|
92
|
+
return self.enabled
|
|
93
|
+
|
|
94
|
+
def __repr__(self):
|
|
95
|
+
"""String representation of hook."""
|
|
96
|
+
return f"{self.__class__.__name__}(name='{self.name}', priority={self.priority}, enabled={self.enabled})"
|
|
97
|
+
|
|
98
|
+
def __lt__(self, other):
|
|
99
|
+
"""Compare hooks by priority for sorting."""
|
|
100
|
+
if not isinstance(other, BaseHook):
|
|
101
|
+
return NotImplemented
|
|
102
|
+
return self.priority < other.priority
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class SubmitHook(BaseHook):
|
|
106
|
+
"""Base class for hooks that process user prompts."""
|
|
107
|
+
|
|
108
|
+
def __init__(self, name: str, priority: int = 50):
|
|
109
|
+
super().__init__(name, priority)
|
|
110
|
+
|
|
111
|
+
def validate(self, context: HookContext) -> bool:
|
|
112
|
+
"""Validate submit hook context."""
|
|
113
|
+
if not super().validate(context):
|
|
114
|
+
return False
|
|
115
|
+
return context.hook_type == HookType.SUBMIT and 'prompt' in context.data
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class PreDelegationHook(BaseHook):
|
|
119
|
+
"""Base class for hooks that filter context before delegation."""
|
|
120
|
+
|
|
121
|
+
def __init__(self, name: str, priority: int = 50):
|
|
122
|
+
super().__init__(name, priority)
|
|
123
|
+
|
|
124
|
+
def validate(self, context: HookContext) -> bool:
|
|
125
|
+
"""Validate pre-delegation hook context."""
|
|
126
|
+
if not super().validate(context):
|
|
127
|
+
return False
|
|
128
|
+
return context.hook_type == HookType.PRE_DELEGATION and 'agent' in context.data
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class PostDelegationHook(BaseHook):
|
|
132
|
+
"""Base class for hooks that process results after delegation."""
|
|
133
|
+
|
|
134
|
+
def __init__(self, name: str, priority: int = 50):
|
|
135
|
+
super().__init__(name, priority)
|
|
136
|
+
|
|
137
|
+
def validate(self, context: HookContext) -> bool:
|
|
138
|
+
"""Validate post-delegation hook context."""
|
|
139
|
+
if not super().validate(context):
|
|
140
|
+
return False
|
|
141
|
+
return context.hook_type == HookType.POST_DELEGATION and 'result' in context.data
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TicketExtractionHook(BaseHook):
|
|
145
|
+
"""Base class for hooks that extract and create tickets."""
|
|
146
|
+
|
|
147
|
+
def __init__(self, name: str, priority: int = 50):
|
|
148
|
+
super().__init__(name, priority)
|
|
149
|
+
|
|
150
|
+
def validate(self, context: HookContext) -> bool:
|
|
151
|
+
"""Validate ticket extraction hook context."""
|
|
152
|
+
if not super().validate(context):
|
|
153
|
+
return False
|
|
154
|
+
return context.hook_type == HookType.TICKET_EXTRACTION
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Built-in hooks for claude-mpm."""
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Example logging hook for Claude MPM.
|
|
4
|
+
|
|
5
|
+
This hook demonstrates how to capture and log all prompts and responses
|
|
6
|
+
through the hook system, providing an alternative to built-in logging.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, Any, Optional
|
|
14
|
+
|
|
15
|
+
# Configure your logging directory
|
|
16
|
+
LOG_DIR = Path.home() / ".claude-mpm-hook-logs"
|
|
17
|
+
LOG_DIR.mkdir(exist_ok=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def execute_pre_delegation_hook(agent: str, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
21
|
+
"""
|
|
22
|
+
Log task details before delegation.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
agent: The agent being invoked
|
|
26
|
+
context: Contains 'task' and other context data
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Empty dict (no modifications)
|
|
30
|
+
"""
|
|
31
|
+
timestamp = datetime.now().isoformat()
|
|
32
|
+
log_entry = {
|
|
33
|
+
"timestamp": timestamp,
|
|
34
|
+
"event": "pre_delegation",
|
|
35
|
+
"agent": agent,
|
|
36
|
+
"task": context.get("task", ""),
|
|
37
|
+
"context": context
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Write to daily log file
|
|
41
|
+
log_file = LOG_DIR / f"delegations_{datetime.now().strftime('%Y%m%d')}.jsonl"
|
|
42
|
+
with open(log_file, 'a') as f:
|
|
43
|
+
f.write(json.dumps(log_entry) + '\n')
|
|
44
|
+
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def execute_post_delegation_hook(agent: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
49
|
+
"""
|
|
50
|
+
Log complete prompt and response after delegation.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
agent: The agent that was invoked
|
|
54
|
+
result: Contains task, response, execution_time, tokens
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Empty dict (no modifications)
|
|
58
|
+
"""
|
|
59
|
+
timestamp = datetime.now().isoformat()
|
|
60
|
+
|
|
61
|
+
# Extract data
|
|
62
|
+
task = result.get("task", "")
|
|
63
|
+
response = result.get("response", "")
|
|
64
|
+
execution_time = result.get("execution_time", 0)
|
|
65
|
+
tokens = result.get("tokens", 0)
|
|
66
|
+
|
|
67
|
+
# Create detailed log entry
|
|
68
|
+
log_entry = {
|
|
69
|
+
"timestamp": timestamp,
|
|
70
|
+
"event": "post_delegation",
|
|
71
|
+
"agent": agent,
|
|
72
|
+
"task": task,
|
|
73
|
+
"response_length": len(response),
|
|
74
|
+
"execution_time": execution_time,
|
|
75
|
+
"tokens": tokens,
|
|
76
|
+
"success": not response.startswith("Error:"),
|
|
77
|
+
"response_preview": response[:500] + "..." if len(response) > 500 else response
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Write to agent-specific log
|
|
81
|
+
agent_log_dir = LOG_DIR / "agents" / agent.lower()
|
|
82
|
+
agent_log_dir.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
|
|
84
|
+
log_file = agent_log_dir / f"{datetime.now().strftime('%Y%m%d')}.jsonl"
|
|
85
|
+
with open(log_file, 'a') as f:
|
|
86
|
+
f.write(json.dumps(log_entry) + '\n')
|
|
87
|
+
|
|
88
|
+
# Save full prompt/response if needed
|
|
89
|
+
if os.environ.get("CLAUDE_MPM_HOOK_LOG_FULL", "").lower() == "true":
|
|
90
|
+
# Create unique filename
|
|
91
|
+
task_hash = str(hash(task))[-8:]
|
|
92
|
+
prompt_file = agent_log_dir / f"prompt_{timestamp}_{task_hash}.txt"
|
|
93
|
+
response_file = agent_log_dir / f"response_{timestamp}_{task_hash}.txt"
|
|
94
|
+
|
|
95
|
+
# Note: We don't have access to the original prompt in post-delegation
|
|
96
|
+
# To capture prompts, you'd need to store them in pre-delegation
|
|
97
|
+
# and match them up using task hash or similar
|
|
98
|
+
|
|
99
|
+
response_file.write_text(response)
|
|
100
|
+
|
|
101
|
+
log_entry["response_file"] = str(response_file)
|
|
102
|
+
|
|
103
|
+
return {}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def execute_submit_hook(prompt: str, session_type: str) -> Dict[str, Any]:
|
|
107
|
+
"""
|
|
108
|
+
Log user prompts at session start.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
prompt: The user's input prompt
|
|
112
|
+
session_type: Type of session (e.g., "subprocess")
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Empty dict (no modifications)
|
|
116
|
+
"""
|
|
117
|
+
timestamp = datetime.now().isoformat()
|
|
118
|
+
log_entry = {
|
|
119
|
+
"timestamp": timestamp,
|
|
120
|
+
"event": "user_submit",
|
|
121
|
+
"session_type": session_type,
|
|
122
|
+
"prompt": prompt,
|
|
123
|
+
"prompt_length": len(prompt)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Write to session log
|
|
127
|
+
log_file = LOG_DIR / f"sessions_{datetime.now().strftime('%Y%m%d')}.jsonl"
|
|
128
|
+
with open(log_file, 'a') as f:
|
|
129
|
+
f.write(json.dumps(log_entry) + '\n')
|
|
130
|
+
|
|
131
|
+
print(f"[Logging Hook] Logged user prompt to {log_file}")
|
|
132
|
+
|
|
133
|
+
return {}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Optional: Hook metadata for registration
|
|
137
|
+
HOOK_METADATA = {
|
|
138
|
+
"name": "logging_hook",
|
|
139
|
+
"description": "Comprehensive logging of all prompts and responses",
|
|
140
|
+
"version": "1.0.0",
|
|
141
|
+
"author": "claude-mpm",
|
|
142
|
+
"events": ["pre_delegation", "post_delegation", "submit"],
|
|
143
|
+
"config": {
|
|
144
|
+
"log_dir": str(LOG_DIR),
|
|
145
|
+
"full_logging": os.environ.get("CLAUDE_MPM_HOOK_LOG_FULL", "false")
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
# Test the hook
|
|
152
|
+
print(f"Logging hook configured to write to: {LOG_DIR}")
|
|
153
|
+
print("Set CLAUDE_MPM_HOOK_LOG_FULL=true to save complete responses")
|
|
154
|
+
|
|
155
|
+
# Example usage
|
|
156
|
+
execute_submit_hook("Test prompt", "test")
|
|
157
|
+
execute_pre_delegation_hook("Engineer", {"task": "Test task"})
|
|
158
|
+
execute_post_delegation_hook("Engineer", {
|
|
159
|
+
"task": "Test task",
|
|
160
|
+
"response": "Test response",
|
|
161
|
+
"execution_time": 1.5,
|
|
162
|
+
"tokens": 100
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
print(f"\nCheck logs in: {LOG_DIR}")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Example post-delegation hook implementation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from typing import Dict, Any, List
|
|
6
|
+
|
|
7
|
+
from claude_mpm.hooks.base_hook import PostDelegationHook, HookContext, HookResult
|
|
8
|
+
from claude_mpm.core.logger import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ResultValidatorHook(PostDelegationHook):
|
|
14
|
+
"""Hook that validates agent results for quality and completeness."""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
super().__init__(name="result_validator", priority=10)
|
|
18
|
+
|
|
19
|
+
def execute(self, context: HookContext) -> HookResult:
|
|
20
|
+
"""Validate agent results."""
|
|
21
|
+
try:
|
|
22
|
+
result = context.data.get('result', {})
|
|
23
|
+
agent = context.data.get('agent', 'unknown')
|
|
24
|
+
|
|
25
|
+
# Validation checks
|
|
26
|
+
issues = []
|
|
27
|
+
|
|
28
|
+
# Check for empty results
|
|
29
|
+
if not result:
|
|
30
|
+
issues.append("Empty result returned")
|
|
31
|
+
|
|
32
|
+
# Check for error indicators
|
|
33
|
+
error_patterns = ['error', 'failed', 'exception', 'traceback']
|
|
34
|
+
result_str = json.dumps(result).lower()
|
|
35
|
+
for pattern in error_patterns:
|
|
36
|
+
if pattern in result_str and 'success' not in result:
|
|
37
|
+
issues.append(f"Result contains '{pattern}' indicator")
|
|
38
|
+
|
|
39
|
+
# Agent-specific validation
|
|
40
|
+
if agent.lower() == 'engineer' and 'code' in str(result):
|
|
41
|
+
# Check for code quality indicators
|
|
42
|
+
if 'todo' in result_str or 'fixme' in result_str:
|
|
43
|
+
issues.append("Code contains TODO/FIXME comments")
|
|
44
|
+
|
|
45
|
+
if issues:
|
|
46
|
+
logger.warning(f"Validation issues found: {issues}")
|
|
47
|
+
return HookResult(
|
|
48
|
+
success=True,
|
|
49
|
+
data={
|
|
50
|
+
'result': result,
|
|
51
|
+
'validation_issues': issues
|
|
52
|
+
},
|
|
53
|
+
modified=True,
|
|
54
|
+
metadata={'issues_count': len(issues)}
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
return HookResult(
|
|
58
|
+
success=True,
|
|
59
|
+
data=context.data,
|
|
60
|
+
modified=False,
|
|
61
|
+
metadata={'validated': True}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Result validation failed: {e}")
|
|
66
|
+
return HookResult(
|
|
67
|
+
success=False,
|
|
68
|
+
error=str(e)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ResultMetricsHook(PostDelegationHook):
|
|
73
|
+
"""Hook that collects metrics from agent results."""
|
|
74
|
+
|
|
75
|
+
def __init__(self):
|
|
76
|
+
super().__init__(name="result_metrics", priority=50)
|
|
77
|
+
|
|
78
|
+
def execute(self, context: HookContext) -> HookResult:
|
|
79
|
+
"""Collect metrics from agent results."""
|
|
80
|
+
try:
|
|
81
|
+
result = context.data.get('result', {})
|
|
82
|
+
agent = context.data.get('agent', 'unknown')
|
|
83
|
+
execution_time = context.metadata.get('execution_time_ms', 0)
|
|
84
|
+
|
|
85
|
+
# Collect metrics
|
|
86
|
+
metrics = {
|
|
87
|
+
'agent': agent,
|
|
88
|
+
'execution_time_ms': execution_time,
|
|
89
|
+
'result_size_bytes': len(json.dumps(result).encode()),
|
|
90
|
+
'timestamp': context.timestamp.isoformat()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Agent-specific metrics
|
|
94
|
+
if agent.lower() == 'engineer':
|
|
95
|
+
# Count code-related metrics
|
|
96
|
+
code_content = str(result)
|
|
97
|
+
metrics['lines_of_code'] = code_content.count('\n')
|
|
98
|
+
metrics['functions_created'] = len(re.findall(r'def\s+\w+', code_content))
|
|
99
|
+
metrics['classes_created'] = len(re.findall(r'class\s+\w+', code_content))
|
|
100
|
+
|
|
101
|
+
elif agent.lower() == 'qa':
|
|
102
|
+
# Count test-related metrics
|
|
103
|
+
test_content = str(result)
|
|
104
|
+
metrics['tests_count'] = len(re.findall(r'test_\w+', test_content))
|
|
105
|
+
metrics['assertions_count'] = len(re.findall(r'assert\s+', test_content))
|
|
106
|
+
|
|
107
|
+
logger.info(f"Collected metrics: {metrics}")
|
|
108
|
+
|
|
109
|
+
return HookResult(
|
|
110
|
+
success=True,
|
|
111
|
+
data={
|
|
112
|
+
'result': result,
|
|
113
|
+
'metrics': metrics
|
|
114
|
+
},
|
|
115
|
+
modified=True,
|
|
116
|
+
metadata=metrics
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"Metrics collection failed: {e}")
|
|
121
|
+
return HookResult(
|
|
122
|
+
success=False,
|
|
123
|
+
error=str(e)
|
|
124
|
+
)
|