claude-mpm 0.3.0__py3-none-any.whl → 1.1.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.
Files changed (42) hide show
  1. claude_mpm/_version.py +3 -2
  2. claude_mpm/agents/INSTRUCTIONS.md +23 -0
  3. claude_mpm/agents/__init__.py +2 -2
  4. claude_mpm/agents/agent-template.yaml +83 -0
  5. claude_mpm/agents/agent_loader.py +66 -90
  6. claude_mpm/agents/base_agent_loader.py +10 -15
  7. claude_mpm/cli.py +41 -47
  8. claude_mpm/cli_enhancements.py +297 -0
  9. claude_mpm/core/agent_name_normalizer.py +49 -0
  10. claude_mpm/core/factories.py +1 -46
  11. claude_mpm/core/service_registry.py +0 -8
  12. claude_mpm/core/simple_runner.py +50 -0
  13. claude_mpm/generators/__init__.py +5 -0
  14. claude_mpm/generators/agent_profile_generator.py +137 -0
  15. claude_mpm/hooks/README.md +75 -221
  16. claude_mpm/hooks/builtin/mpm_command_hook.py +125 -0
  17. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +8 -7
  18. claude_mpm/hooks/claude_hooks/__init__.py +5 -0
  19. claude_mpm/hooks/claude_hooks/hook_handler.py +399 -0
  20. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +47 -0
  21. claude_mpm/hooks/validation_hooks.py +181 -0
  22. claude_mpm/services/agent_management_service.py +4 -4
  23. claude_mpm/services/agent_profile_loader.py +1 -1
  24. claude_mpm/services/agent_registry.py +0 -1
  25. claude_mpm/services/base_agent_manager.py +3 -3
  26. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +57 -31
  27. claude_mpm/utils/error_handler.py +247 -0
  28. claude_mpm/validation/__init__.py +5 -0
  29. claude_mpm/validation/agent_validator.py +175 -0
  30. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/METADATA +44 -7
  31. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/RECORD +34 -30
  32. claude_mpm/config/hook_config.py +0 -42
  33. claude_mpm/hooks/hook_client.py +0 -264
  34. claude_mpm/hooks/hook_runner.py +0 -370
  35. claude_mpm/hooks/json_rpc_executor.py +0 -259
  36. claude_mpm/hooks/json_rpc_hook_client.py +0 -319
  37. claude_mpm/services/hook_service.py +0 -388
  38. claude_mpm/services/hook_service_manager.py +0 -223
  39. claude_mpm/services/json_rpc_hook_manager.py +0 -92
  40. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/WHEEL +0 -0
  41. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/entry_points.txt +0 -0
  42. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/top_level.txt +0 -0
@@ -1,319 +0,0 @@
1
- """JSON-RPC based client for executing hooks without HTTP server."""
2
-
3
- import importlib.util
4
- import inspect
5
- import logging
6
- from pathlib import Path
7
- from typing import Any, Dict, List, Optional
8
-
9
- from claude_mpm.hooks.base_hook import (
10
- BaseHook, HookType,
11
- SubmitHook, PreDelegationHook, PostDelegationHook, TicketExtractionHook
12
- )
13
- from claude_mpm.hooks.json_rpc_executor import JSONRPCHookExecutor, JSONRPCError
14
- from claude_mpm.core.logger import get_logger
15
-
16
- logger = get_logger(__name__)
17
-
18
-
19
- class JSONRPCHookClient:
20
- """Client for executing hooks via JSON-RPC subprocess calls."""
21
-
22
- def __init__(self, timeout: int = 30):
23
- """Initialize JSON-RPC hook client.
24
-
25
- Args:
26
- timeout: Request timeout in seconds
27
- """
28
- self.timeout = timeout
29
- self.executor = JSONRPCHookExecutor(timeout=timeout)
30
- self._hook_registry = {}
31
- self._hook_types = {}
32
- self._discover_hooks()
33
-
34
- def _discover_hooks(self):
35
- """Discover available hooks from builtin directory."""
36
- hooks_dir = Path(__file__).parent / 'builtin'
37
- if not hooks_dir.exists():
38
- logger.warning(f"Builtin hooks directory not found: {hooks_dir}")
39
- return
40
-
41
- for hook_file in hooks_dir.glob('*.py'):
42
- if hook_file.name.startswith('_'):
43
- continue
44
-
45
- try:
46
- # Load the module to discover hooks
47
- module_name = hook_file.stem
48
- spec = importlib.util.spec_from_file_location(module_name, hook_file)
49
- if spec and spec.loader:
50
- module = importlib.util.module_from_spec(spec)
51
- spec.loader.exec_module(module)
52
-
53
- # Find hook classes (don't instantiate, just register)
54
- for name, obj in inspect.getmembers(module):
55
- if (inspect.isclass(obj) and
56
- issubclass(obj, BaseHook) and
57
- obj not in [BaseHook, SubmitHook, PreDelegationHook,
58
- PostDelegationHook, TicketExtractionHook] and
59
- not name.startswith('_')):
60
- # Create temporary instance to get name
61
- temp_instance = obj()
62
- hook_name = temp_instance.name
63
-
64
- # Determine hook type
65
- if isinstance(temp_instance, SubmitHook):
66
- hook_type = HookType.SUBMIT
67
- elif isinstance(temp_instance, PreDelegationHook):
68
- hook_type = HookType.PRE_DELEGATION
69
- elif isinstance(temp_instance, PostDelegationHook):
70
- hook_type = HookType.POST_DELEGATION
71
- elif isinstance(temp_instance, TicketExtractionHook):
72
- hook_type = HookType.TICKET_EXTRACTION
73
- else:
74
- hook_type = HookType.CUSTOM
75
-
76
- self._hook_registry[hook_name] = {
77
- 'name': hook_name,
78
- 'type': hook_type,
79
- 'priority': temp_instance.priority,
80
- 'class': name,
81
- 'module': module_name
82
- }
83
- self._hook_types[hook_type] = self._hook_types.get(hook_type, [])
84
- self._hook_types[hook_type].append(hook_name)
85
-
86
- logger.debug(f"Discovered hook '{hook_name}' of type {hook_type.value}")
87
-
88
- except Exception as e:
89
- logger.error(f"Failed to discover hooks from {hook_file}: {e}")
90
-
91
- def health_check(self) -> Dict[str, Any]:
92
- """Check health of hook system.
93
-
94
- Returns:
95
- Health status dictionary
96
- """
97
- try:
98
- hook_count = len(self._hook_registry)
99
- return {
100
- 'status': 'healthy',
101
- 'hook_count': hook_count,
102
- 'executor': 'json-rpc',
103
- 'discovered_hooks': list(self._hook_registry.keys())
104
- }
105
- except Exception as e:
106
- logger.error(f"Health check failed: {e}")
107
- return {
108
- 'status': 'unhealthy',
109
- 'error': str(e)
110
- }
111
-
112
- def list_hooks(self) -> Dict[str, List[Dict[str, Any]]]:
113
- """List all registered hooks.
114
-
115
- Returns:
116
- Dictionary mapping hook types to hook info
117
- """
118
- hooks_by_type = {}
119
-
120
- for hook_type in HookType:
121
- hooks_by_type[hook_type.value] = []
122
-
123
- for hook_name, hook_info in self._hook_registry.items():
124
- hook_type = hook_info['type']
125
- hooks_by_type[hook_type.value].append({
126
- 'name': hook_name,
127
- 'priority': hook_info['priority'],
128
- 'enabled': True # All discovered hooks are enabled
129
- })
130
-
131
- # Sort by priority
132
- for hook_list in hooks_by_type.values():
133
- hook_list.sort(key=lambda x: x['priority'])
134
-
135
- return hooks_by_type
136
-
137
- def execute_hook(self, hook_type: HookType, context_data: Dict[str, Any],
138
- metadata: Optional[Dict[str, Any]] = None,
139
- specific_hook: Optional[str] = None) -> List[Dict[str, Any]]:
140
- """Execute hooks of a given type.
141
-
142
- Args:
143
- hook_type: Type of hooks to execute
144
- context_data: Data to pass to hooks
145
- metadata: Optional metadata
146
- specific_hook: Optional specific hook name to execute
147
-
148
- Returns:
149
- List of execution results
150
- """
151
- try:
152
- if specific_hook:
153
- # Execute specific hook
154
- if specific_hook not in self._hook_registry:
155
- logger.error(f"Hook '{specific_hook}' not found")
156
- return [{
157
- 'hook_name': specific_hook,
158
- 'success': False,
159
- 'error': f"Hook '{specific_hook}' not found"
160
- }]
161
-
162
- try:
163
- result = self.executor.execute_hook(
164
- hook_name=specific_hook,
165
- hook_type=hook_type,
166
- context_data=context_data,
167
- metadata=metadata
168
- )
169
- return [result]
170
- except JSONRPCError as e:
171
- return [{
172
- 'hook_name': specific_hook,
173
- 'success': False,
174
- 'error': e.message,
175
- 'error_code': e.code
176
- }]
177
-
178
- else:
179
- # Execute all hooks of the given type
180
- hook_names = self._hook_types.get(hook_type, [])
181
- if not hook_names:
182
- logger.debug(f"No hooks registered for type {hook_type.value}")
183
- return []
184
-
185
- # Sort by priority
186
- sorted_hooks = sorted(
187
- hook_names,
188
- key=lambda name: self._hook_registry[name]['priority']
189
- )
190
-
191
- return self.executor.execute_hooks(
192
- hook_type=hook_type,
193
- hook_names=sorted_hooks,
194
- context_data=context_data,
195
- metadata=metadata
196
- )
197
-
198
- except Exception as e:
199
- logger.error(f"Failed to execute hooks: {e}")
200
- return [{
201
- 'success': False,
202
- 'error': str(e)
203
- }]
204
-
205
- def execute_submit_hook(self, prompt: str, **kwargs) -> List[Dict[str, Any]]:
206
- """Execute submit hooks on a user prompt.
207
-
208
- Args:
209
- prompt: User prompt to process
210
- **kwargs: Additional context data
211
-
212
- Returns:
213
- List of execution results
214
- """
215
- context_data = {'prompt': prompt}
216
- context_data.update(kwargs)
217
- return self.execute_hook(HookType.SUBMIT, context_data)
218
-
219
- def execute_pre_delegation_hook(self, agent: str, context: Dict[str, Any],
220
- **kwargs) -> List[Dict[str, Any]]:
221
- """Execute pre-delegation hooks.
222
-
223
- Args:
224
- agent: Agent being delegated to
225
- context: Context being passed to agent
226
- **kwargs: Additional data
227
-
228
- Returns:
229
- List of execution results
230
- """
231
- context_data = {
232
- 'agent': agent,
233
- 'context': context
234
- }
235
- context_data.update(kwargs)
236
- return self.execute_hook(HookType.PRE_DELEGATION, context_data)
237
-
238
- def execute_post_delegation_hook(self, agent: str, result: Any,
239
- **kwargs) -> List[Dict[str, Any]]:
240
- """Execute post-delegation hooks.
241
-
242
- Args:
243
- agent: Agent that was delegated to
244
- result: Result from agent
245
- **kwargs: Additional data
246
-
247
- Returns:
248
- List of execution results
249
- """
250
- context_data = {
251
- 'agent': agent,
252
- 'result': result
253
- }
254
- context_data.update(kwargs)
255
- return self.execute_hook(HookType.POST_DELEGATION, context_data)
256
-
257
- def execute_ticket_extraction_hook(self, content: Any,
258
- **kwargs) -> List[Dict[str, Any]]:
259
- """Execute ticket extraction hooks.
260
-
261
- Args:
262
- content: Content to extract tickets from
263
- **kwargs: Additional data
264
-
265
- Returns:
266
- List of execution results
267
- """
268
- context_data = {'content': content}
269
- context_data.update(kwargs)
270
- return self.execute_hook(HookType.TICKET_EXTRACTION, context_data)
271
-
272
- def get_modified_data(self, results: List[Dict[str, Any]]) -> Dict[str, Any]:
273
- """Extract modified data from hook results.
274
-
275
- Args:
276
- results: Hook execution results
277
-
278
- Returns:
279
- Combined modified data from all hooks
280
- """
281
- modified_data = {}
282
-
283
- for result in results:
284
- if result.get('modified') and result.get('data'):
285
- modified_data.update(result['data'])
286
-
287
- return modified_data
288
-
289
- def get_extracted_tickets(self, results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
290
- """Extract tickets from hook results.
291
-
292
- Args:
293
- results: Hook execution results
294
-
295
- Returns:
296
- List of extracted tickets
297
- """
298
- all_tickets = []
299
-
300
- for result in results:
301
- if result.get('success') and 'tickets' in result.get('data', {}):
302
- tickets = result['data']['tickets']
303
- if isinstance(tickets, list):
304
- all_tickets.extend(tickets)
305
-
306
- return all_tickets
307
-
308
-
309
- # Update the convenience function to use JSON-RPC client
310
- def get_hook_client(base_url: Optional[str] = None) -> JSONRPCHookClient:
311
- """Get a hook client instance.
312
-
313
- Args:
314
- base_url: Ignored for JSON-RPC client (kept for compatibility)
315
-
316
- Returns:
317
- JSONRPCHookClient instance
318
- """
319
- return JSONRPCHookClient()
@@ -1,388 +0,0 @@
1
- """Centralized hook service for claude-mpm."""
2
-
3
- import asyncio
4
- import json
5
- import logging
6
- import os
7
- import sys
8
- import time
9
- import traceback
10
- from collections import defaultdict
11
- from datetime import datetime
12
- from pathlib import Path
13
- from typing import Any, Dict, List, Optional, Set, Type, Union
14
-
15
- from flask import Flask, jsonify, request
16
- from flask_cors import CORS
17
-
18
- # Add parent directory to path for imports
19
- sys.path.insert(0, str(Path(__file__).parent.parent.parent))
20
-
21
- from claude_mpm.hooks.base_hook import (
22
- BaseHook, HookContext, HookResult, HookType,
23
- SubmitHook, PreDelegationHook, PostDelegationHook, TicketExtractionHook
24
- )
25
- from claude_mpm.core.logger import get_logger
26
-
27
- logger = get_logger(__name__)
28
-
29
-
30
- class HookRegistry:
31
- """Registry for managing hooks."""
32
-
33
- def __init__(self):
34
- """Initialize empty hook registry."""
35
- self._hooks: Dict[HookType, List[BaseHook]] = defaultdict(list)
36
- self._hook_instances: Dict[str, BaseHook] = {}
37
- self._lock = asyncio.Lock()
38
-
39
- def register(self, hook: BaseHook, hook_type: Optional[HookType] = None) -> bool:
40
- """Register a hook instance.
41
-
42
- Args:
43
- hook: Hook instance to register
44
- hook_type: Optional hook type override
45
-
46
- Returns:
47
- True if registered successfully
48
- """
49
- try:
50
- # Determine hook type
51
- if hook_type is None:
52
- if isinstance(hook, SubmitHook):
53
- hook_type = HookType.SUBMIT
54
- elif isinstance(hook, PreDelegationHook):
55
- hook_type = HookType.PRE_DELEGATION
56
- elif isinstance(hook, PostDelegationHook):
57
- hook_type = HookType.POST_DELEGATION
58
- elif isinstance(hook, TicketExtractionHook):
59
- hook_type = HookType.TICKET_EXTRACTION
60
- else:
61
- hook_type = HookType.CUSTOM
62
-
63
- # Check for duplicate names
64
- if hook.name in self._hook_instances:
65
- logger.warning(f"Hook '{hook.name}' already registered, replacing")
66
- self.unregister(hook.name)
67
-
68
- # Register hook
69
- self._hooks[hook_type].append(hook)
70
- self._hook_instances[hook.name] = hook
71
-
72
- # Sort by priority
73
- self._hooks[hook_type].sort()
74
-
75
- logger.info(f"Registered hook '{hook.name}' for type {hook_type.value}")
76
- return True
77
-
78
- except Exception as e:
79
- logger.error(f"Failed to register hook '{hook.name}': {e}")
80
- return False
81
-
82
- def unregister(self, hook_name: str) -> bool:
83
- """Unregister a hook by name.
84
-
85
- Args:
86
- hook_name: Name of hook to unregister
87
-
88
- Returns:
89
- True if unregistered successfully
90
- """
91
- try:
92
- if hook_name not in self._hook_instances:
93
- logger.warning(f"Hook '{hook_name}' not found")
94
- return False
95
-
96
- hook = self._hook_instances[hook_name]
97
-
98
- # Remove from type list
99
- for hook_list in self._hooks.values():
100
- if hook in hook_list:
101
- hook_list.remove(hook)
102
-
103
- # Remove from instances
104
- del self._hook_instances[hook_name]
105
-
106
- logger.info(f"Unregistered hook '{hook_name}'")
107
- return True
108
-
109
- except Exception as e:
110
- logger.error(f"Failed to unregister hook '{hook_name}': {e}")
111
- return False
112
-
113
- def get_hooks(self, hook_type: HookType) -> List[BaseHook]:
114
- """Get all hooks for a given type.
115
-
116
- Args:
117
- hook_type: Type of hooks to retrieve
118
-
119
- Returns:
120
- List of hooks sorted by priority
121
- """
122
- return [h for h in self._hooks[hook_type] if h.enabled]
123
-
124
- def get_hook(self, hook_name: str) -> Optional[BaseHook]:
125
- """Get a specific hook by name.
126
-
127
- Args:
128
- hook_name: Name of hook to retrieve
129
-
130
- Returns:
131
- Hook instance or None if not found
132
- """
133
- return self._hook_instances.get(hook_name)
134
-
135
- def list_hooks(self) -> Dict[str, List[Dict[str, Any]]]:
136
- """List all registered hooks.
137
-
138
- Returns:
139
- Dictionary mapping hook types to hook info
140
- """
141
- result = {}
142
- for hook_type, hooks in self._hooks.items():
143
- result[hook_type.value] = [
144
- {
145
- 'name': h.name,
146
- 'priority': h.priority,
147
- 'enabled': h.enabled,
148
- 'class': h.__class__.__name__
149
- }
150
- for h in hooks
151
- ]
152
- return result
153
-
154
-
155
- class HookService:
156
- """Centralized service for managing and executing hooks."""
157
-
158
- def __init__(self, port: int = 5001):
159
- """Initialize hook service.
160
-
161
- Args:
162
- port: Port to run service on
163
- """
164
- self.port = port
165
- self.registry = HookRegistry()
166
- self.app = Flask(__name__)
167
- CORS(self.app)
168
- self._setup_routes()
169
- self._load_builtin_hooks()
170
-
171
- def _setup_routes(self):
172
- """Setup Flask routes for hook service."""
173
-
174
- @self.app.route('/health', methods=['GET'])
175
- def health():
176
- """Health check endpoint."""
177
- return jsonify({
178
- 'status': 'healthy',
179
- 'timestamp': datetime.now().isoformat(),
180
- 'hooks_count': sum(len(h) for h in self.registry._hooks.values())
181
- })
182
-
183
- @self.app.route('/hooks/list', methods=['GET'])
184
- def list_hooks():
185
- """List all registered hooks."""
186
- return jsonify({
187
- 'status': 'success',
188
- 'hooks': self.registry.list_hooks()
189
- })
190
-
191
- @self.app.route('/hooks/execute', methods=['POST'])
192
- def execute_hook():
193
- """Execute a specific hook or all hooks of a type."""
194
- try:
195
- data = request.json
196
- hook_type = HookType(data.get('hook_type'))
197
- context_data = data.get('context', {})
198
- metadata = data.get('metadata', {})
199
- specific_hook = data.get('hook_name')
200
-
201
- # Create context
202
- context = HookContext(
203
- hook_type=hook_type,
204
- data=context_data,
205
- metadata=metadata,
206
- timestamp=datetime.now()
207
- )
208
-
209
- # Execute hooks
210
- if specific_hook:
211
- hook = self.registry.get_hook(specific_hook)
212
- if not hook:
213
- return jsonify({
214
- 'status': 'error',
215
- 'error': f"Hook '{specific_hook}' not found"
216
- }), 404
217
- results = [self._execute_single_hook(hook, context)]
218
- else:
219
- results = self._execute_hooks(hook_type, context)
220
-
221
- return jsonify({
222
- 'status': 'success',
223
- 'results': results
224
- })
225
-
226
- except Exception as e:
227
- logger.error(f"Hook execution error: {e}")
228
- return jsonify({
229
- 'status': 'error',
230
- 'error': str(e)
231
- }), 500
232
-
233
- @self.app.route('/hooks/register', methods=['POST'])
234
- def register_hook():
235
- """Register a new hook (for dynamic registration)."""
236
- try:
237
- data = request.json
238
- # This would need to dynamically create hook instances
239
- # For now, return not implemented
240
- return jsonify({
241
- 'status': 'error',
242
- 'error': 'Dynamic registration not yet implemented'
243
- }), 501
244
-
245
- except Exception as e:
246
- logger.error(f"Hook registration error: {e}")
247
- return jsonify({
248
- 'status': 'error',
249
- 'error': str(e)
250
- }), 500
251
-
252
- def _load_builtin_hooks(self):
253
- """Load built-in hooks from hooks directory."""
254
- import importlib.util
255
- import inspect
256
-
257
- hooks_dir = Path(__file__).parent.parent / 'hooks' / 'builtin'
258
- if hooks_dir.exists():
259
- for hook_file in hooks_dir.glob('*.py'):
260
- if hook_file.name.startswith('_'):
261
- continue
262
- try:
263
- # Load the module
264
- module_name = hook_file.stem
265
- spec = importlib.util.spec_from_file_location(module_name, hook_file)
266
- module = importlib.util.module_from_spec(spec)
267
- spec.loader.exec_module(module)
268
-
269
- # Find and instantiate hook classes
270
- for name, obj in inspect.getmembers(module):
271
- if (inspect.isclass(obj) and
272
- issubclass(obj, BaseHook) and
273
- obj is not BaseHook and
274
- not name.startswith('_')):
275
- # Instantiate and register the hook
276
- hook_instance = obj()
277
- self.registry.register(hook_instance)
278
- logger.info(f"Loaded hook '{hook_instance.name}' from {hook_file}")
279
-
280
- except Exception as e:
281
- logger.error(f"Failed to load hook from {hook_file}: {e}")
282
-
283
- def _execute_single_hook(self, hook: BaseHook, context: HookContext) -> Dict[str, Any]:
284
- """Execute a single hook.
285
-
286
- Args:
287
- hook: Hook to execute
288
- context: Context for execution
289
-
290
- Returns:
291
- Execution result dictionary
292
- """
293
- start_time = time.time()
294
- try:
295
- # Validate hook
296
- if not hook.validate(context):
297
- return {
298
- 'hook_name': hook.name,
299
- 'success': False,
300
- 'error': 'Validation failed',
301
- 'execution_time_ms': 0
302
- }
303
-
304
- # Execute hook
305
- if hasattr(hook, '_async') and hook._async:
306
- # Run async hook
307
- loop = asyncio.new_event_loop()
308
- asyncio.set_event_loop(loop)
309
- result = loop.run_until_complete(hook.async_execute(context))
310
- loop.close()
311
- else:
312
- result = hook.execute(context)
313
-
314
- # Add execution time
315
- execution_time = (time.time() - start_time) * 1000
316
- result.execution_time_ms = execution_time
317
-
318
- return {
319
- 'hook_name': hook.name,
320
- 'success': result.success,
321
- 'data': result.data,
322
- 'error': result.error,
323
- 'modified': result.modified,
324
- 'metadata': result.metadata,
325
- 'execution_time_ms': execution_time
326
- }
327
-
328
- except Exception as e:
329
- logger.error(f"Hook '{hook.name}' execution failed: {e}")
330
- return {
331
- 'hook_name': hook.name,
332
- 'success': False,
333
- 'error': str(e),
334
- 'traceback': traceback.format_exc(),
335
- 'execution_time_ms': (time.time() - start_time) * 1000
336
- }
337
-
338
- def _execute_hooks(self, hook_type: HookType, context: HookContext) -> List[Dict[str, Any]]:
339
- """Execute all hooks of a given type.
340
-
341
- Args:
342
- hook_type: Type of hooks to execute
343
- context: Context for execution
344
-
345
- Returns:
346
- List of execution results
347
- """
348
- hooks = self.registry.get_hooks(hook_type)
349
- results = []
350
-
351
- for hook in hooks:
352
- result = self._execute_single_hook(hook, context)
353
- results.append(result)
354
-
355
- # If hook modified data, update context for next hook
356
- if result.get('modified') and result.get('data'):
357
- context.data.update(result['data'])
358
-
359
- return results
360
-
361
- def run(self):
362
- """Run the hook service."""
363
- logger.info(f"Starting hook service on port {self.port}")
364
- self.app.run(host='0.0.0.0', port=self.port, debug=False)
365
-
366
-
367
- def main():
368
- """Main entry point for hook service."""
369
- import argparse
370
-
371
- parser = argparse.ArgumentParser(description='Claude MPM Hook Service')
372
- parser.add_argument('--port', type=int, default=5001, help='Port to run service on')
373
- parser.add_argument('--log-level', default='INFO', help='Logging level')
374
- args = parser.parse_args()
375
-
376
- # Configure logging
377
- logging.basicConfig(
378
- level=getattr(logging, args.log_level.upper()),
379
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
380
- )
381
-
382
- # Create and run service
383
- service = HookService(port=args.port)
384
- service.run()
385
-
386
-
387
- if __name__ == '__main__':
388
- main()