claude-mpm 4.8.0__py3-none-any.whl → 4.8.3__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/templates/golang_engineer.json +257 -0
- claude_mpm/agents/templates/nextjs_engineer.json +122 -132
- claude_mpm/agents/templates/php-engineer.json +258 -175
- claude_mpm/agents/templates/product_owner.json +335 -0
- claude_mpm/agents/templates/python_engineer.json +150 -80
- claude_mpm/agents/templates/ruby-engineer.json +115 -191
- claude_mpm/agents/templates/rust_engineer.json +257 -0
- claude_mpm/agents/templates/typescript_engineer.json +102 -124
- claude_mpm/hooks/__init__.py +14 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
- claude_mpm/hooks/failure_learning/__init__.py +60 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
- claude_mpm/services/memory/failure_tracker.py +563 -0
- claude_mpm/services/memory_hook_service.py +76 -0
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/METADATA +1 -1
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/RECORD +24 -16
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,235 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Failure Detection Hook
|
4
|
+
======================
|
5
|
+
|
6
|
+
Detects task failures from tool outputs and registers them with the FailureTracker.
|
7
|
+
|
8
|
+
WHY: Failures are the first step in the failure-learning cycle. By detecting
|
9
|
+
failures early, we can match them with fixes later and extract valuable learnings.
|
10
|
+
|
11
|
+
DESIGN DECISION: This hook runs after tool execution (priority=85) to inspect
|
12
|
+
tool outputs for failure patterns. It integrates with the PostDelegationHook
|
13
|
+
lifecycle to access tool execution results.
|
14
|
+
|
15
|
+
Integration points:
|
16
|
+
- Monitors post_tool events for Bash, NotebookEdit, and other execution tools
|
17
|
+
- Extracts error messages, exceptions, and test failures
|
18
|
+
- Registers failures with FailureTracker for fix matching
|
19
|
+
"""
|
20
|
+
|
21
|
+
import logging
|
22
|
+
from typing import Any, Dict
|
23
|
+
|
24
|
+
from claude_mpm.hooks.base_hook import (
|
25
|
+
BaseHook,
|
26
|
+
HookContext,
|
27
|
+
HookResult,
|
28
|
+
HookType,
|
29
|
+
)
|
30
|
+
from claude_mpm.services.memory.failure_tracker import get_failure_tracker
|
31
|
+
|
32
|
+
logger = logging.getLogger(__name__)
|
33
|
+
|
34
|
+
|
35
|
+
class FailureDetectionHook(BaseHook):
|
36
|
+
"""Hook that detects and tracks task failures.
|
37
|
+
|
38
|
+
WHY: Automatically captures failures without requiring manual tracking.
|
39
|
+
Runs after tool execution to inspect outputs and detect error patterns.
|
40
|
+
|
41
|
+
DESIGN DECISION: Priority 85 ensures this runs after tool execution but
|
42
|
+
before fix detection and learning extraction. This ordering is critical
|
43
|
+
for the failure-learning cycle.
|
44
|
+
"""
|
45
|
+
|
46
|
+
# Tools to monitor for failures
|
47
|
+
MONITORED_TOOLS = [
|
48
|
+
"Bash", # Command execution
|
49
|
+
"NotebookEdit", # Jupyter notebook execution
|
50
|
+
"Task", # Subagent delegation
|
51
|
+
]
|
52
|
+
|
53
|
+
def __init__(self):
|
54
|
+
"""Initialize the failure detection hook."""
|
55
|
+
super().__init__(
|
56
|
+
name="failure_detection",
|
57
|
+
priority=85, # After tool execution, before fix detection
|
58
|
+
)
|
59
|
+
self.tracker = get_failure_tracker()
|
60
|
+
|
61
|
+
def execute(self, context: HookContext) -> HookResult:
|
62
|
+
"""Execute failure detection on tool output.
|
63
|
+
|
64
|
+
WHY: Inspects tool execution results to identify failures. When a failure
|
65
|
+
is detected, it's registered with the FailureTracker for later matching
|
66
|
+
with fixes.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
context: Hook context containing tool execution data
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
HookResult with detection results
|
73
|
+
"""
|
74
|
+
try:
|
75
|
+
# Extract tool execution data
|
76
|
+
tool_name = context.data.get("tool_name")
|
77
|
+
tool_output = self._extract_tool_output(context.data)
|
78
|
+
exit_code = context.data.get("exit_code", 0)
|
79
|
+
|
80
|
+
# Only process monitored tools
|
81
|
+
if tool_name not in self.MONITORED_TOOLS:
|
82
|
+
return HookResult(success=True, modified=False)
|
83
|
+
|
84
|
+
# Skip if tool succeeded
|
85
|
+
if exit_code == 0 and not self._contains_failure_indicators(tool_output):
|
86
|
+
return HookResult(success=True, modified=False)
|
87
|
+
|
88
|
+
# Detect failure
|
89
|
+
failure_context = self._build_failure_context(context)
|
90
|
+
failure = self.tracker.detect_failure(
|
91
|
+
tool_name=tool_name, tool_output=tool_output, context=failure_context
|
92
|
+
)
|
93
|
+
|
94
|
+
if failure:
|
95
|
+
logger.info(
|
96
|
+
f"Failure detected: {failure.task_type} - "
|
97
|
+
f"{failure.error_message[:50]}..."
|
98
|
+
)
|
99
|
+
return HookResult(
|
100
|
+
success=True,
|
101
|
+
modified=False,
|
102
|
+
metadata={"failure_detected": True, "failure_id": failure.task_id},
|
103
|
+
)
|
104
|
+
|
105
|
+
return HookResult(success=True, modified=False)
|
106
|
+
|
107
|
+
except Exception as e:
|
108
|
+
logger.error(f"Error in failure detection hook: {e}", exc_info=True)
|
109
|
+
return HookResult(success=False, error=str(e), modified=False)
|
110
|
+
|
111
|
+
def validate(self, context: HookContext) -> bool:
|
112
|
+
"""Validate if this hook should run for the given context.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
context: Hook context to validate
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
True if hook should execute
|
119
|
+
"""
|
120
|
+
if not super().validate(context):
|
121
|
+
return False
|
122
|
+
|
123
|
+
# Run for POST_DELEGATION events (after tool execution)
|
124
|
+
if context.hook_type != HookType.POST_DELEGATION:
|
125
|
+
return False
|
126
|
+
|
127
|
+
# Must have tool execution data
|
128
|
+
return "tool_name" in context.data
|
129
|
+
|
130
|
+
def _extract_tool_output(self, data: Dict[str, Any]) -> str:
|
131
|
+
"""Extract tool output from event data.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
data: Event data dictionary
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
Tool output string
|
138
|
+
"""
|
139
|
+
# Try various output fields
|
140
|
+
output = (
|
141
|
+
data.get("output")
|
142
|
+
or data.get("result")
|
143
|
+
or data.get("error_output")
|
144
|
+
or data.get("stderr")
|
145
|
+
or ""
|
146
|
+
)
|
147
|
+
|
148
|
+
# Handle nested result structures
|
149
|
+
if isinstance(output, dict):
|
150
|
+
output = (
|
151
|
+
output.get("output")
|
152
|
+
or output.get("content")
|
153
|
+
or output.get("error")
|
154
|
+
or str(output)
|
155
|
+
)
|
156
|
+
|
157
|
+
return str(output) if output else ""
|
158
|
+
|
159
|
+
def _contains_failure_indicators(self, output: str) -> bool:
|
160
|
+
"""Check if output contains failure indicators even if exit_code is 0.
|
161
|
+
|
162
|
+
WHY: Some tools return 0 exit code but still report failures in output
|
163
|
+
(e.g., test runners that catch exceptions).
|
164
|
+
|
165
|
+
Args:
|
166
|
+
output: Tool output
|
167
|
+
|
168
|
+
Returns:
|
169
|
+
True if failure indicators found
|
170
|
+
"""
|
171
|
+
if not output:
|
172
|
+
return False
|
173
|
+
|
174
|
+
failure_keywords = [
|
175
|
+
"error:",
|
176
|
+
"exception:",
|
177
|
+
"failed",
|
178
|
+
"failure",
|
179
|
+
"traceback",
|
180
|
+
"✗",
|
181
|
+
"❌",
|
182
|
+
]
|
183
|
+
|
184
|
+
output_lower = output.lower()
|
185
|
+
return any(keyword in output_lower for keyword in failure_keywords)
|
186
|
+
|
187
|
+
def _build_failure_context(self, context: HookContext) -> Dict[str, str]:
|
188
|
+
"""Build context dictionary for failure event.
|
189
|
+
|
190
|
+
Args:
|
191
|
+
context: Hook context
|
192
|
+
|
193
|
+
Returns:
|
194
|
+
Context dictionary with agent, session, and other info
|
195
|
+
"""
|
196
|
+
failure_context = {}
|
197
|
+
|
198
|
+
# Extract relevant context fields
|
199
|
+
if context.session_id:
|
200
|
+
failure_context["session_id"] = context.session_id
|
201
|
+
|
202
|
+
# Extract agent info from data or metadata
|
203
|
+
agent_type = (
|
204
|
+
context.data.get("agent_type")
|
205
|
+
or context.data.get("subagent_type")
|
206
|
+
or context.metadata.get("agent_type")
|
207
|
+
)
|
208
|
+
if agent_type:
|
209
|
+
failure_context["agent_type"] = agent_type
|
210
|
+
|
211
|
+
# Extract command/script info for Bash tool
|
212
|
+
if context.data.get("tool_name") == "Bash":
|
213
|
+
command = context.data.get("command") or context.data.get(
|
214
|
+
"tool_input", {}
|
215
|
+
).get("command")
|
216
|
+
if command:
|
217
|
+
failure_context["command"] = command
|
218
|
+
|
219
|
+
# Extract working directory
|
220
|
+
working_dir = context.data.get("working_directory") or context.data.get("cwd")
|
221
|
+
if working_dir:
|
222
|
+
failure_context["working_dir"] = working_dir
|
223
|
+
|
224
|
+
return failure_context
|
225
|
+
|
226
|
+
|
227
|
+
def get_failure_detection_hook() -> FailureDetectionHook:
|
228
|
+
"""Factory function to create failure detection hook.
|
229
|
+
|
230
|
+
WHY: Provides consistent hook creation pattern used throughout the framework.
|
231
|
+
|
232
|
+
Returns:
|
233
|
+
Configured FailureDetectionHook instance
|
234
|
+
"""
|
235
|
+
return FailureDetectionHook()
|
@@ -0,0 +1,217 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Fix Detection Hook
|
4
|
+
==================
|
5
|
+
|
6
|
+
Detects when previously failed tasks succeed, indicating a fix has been applied.
|
7
|
+
|
8
|
+
WHY: Detecting fixes is the second step in the failure-learning cycle. When a
|
9
|
+
task that previously failed now succeeds, we know a fix was applied. Matching
|
10
|
+
fixes with failures creates the foundation for learning extraction.
|
11
|
+
|
12
|
+
DESIGN DECISION: This hook runs after failure detection (priority=87) to check
|
13
|
+
if successful tool executions resolve previous failures. Uses the FailureTracker
|
14
|
+
to match fixes with failures.
|
15
|
+
|
16
|
+
Integration points:
|
17
|
+
- Monitors successful tool executions (exit_code=0)
|
18
|
+
- Matches with previously detected failures by task type
|
19
|
+
- Marks failures as fixed in FailureTracker
|
20
|
+
- Triggers learning extraction for failure-fix pairs
|
21
|
+
"""
|
22
|
+
|
23
|
+
import logging
|
24
|
+
from typing import Any, Dict
|
25
|
+
|
26
|
+
from claude_mpm.hooks.base_hook import (
|
27
|
+
BaseHook,
|
28
|
+
HookContext,
|
29
|
+
HookResult,
|
30
|
+
HookType,
|
31
|
+
)
|
32
|
+
from claude_mpm.services.memory.failure_tracker import get_failure_tracker
|
33
|
+
|
34
|
+
logger = logging.getLogger(__name__)
|
35
|
+
|
36
|
+
|
37
|
+
class FixDetectionHook(BaseHook):
|
38
|
+
"""Hook that detects when failures are fixed.
|
39
|
+
|
40
|
+
WHY: Automatically matches successful executions with previous failures.
|
41
|
+
This enables automatic learning extraction without manual intervention.
|
42
|
+
|
43
|
+
DESIGN DECISION: Priority 87 ensures this runs after failure detection
|
44
|
+
but before learning extraction. The sequencing is critical:
|
45
|
+
1. Failure detection (85)
|
46
|
+
2. Fix detection (87)
|
47
|
+
3. Learning extraction (89)
|
48
|
+
"""
|
49
|
+
|
50
|
+
# Tools to monitor for fixes
|
51
|
+
MONITORED_TOOLS = [
|
52
|
+
"Bash", # Command execution
|
53
|
+
"NotebookEdit", # Jupyter notebook execution
|
54
|
+
"Task", # Subagent delegation
|
55
|
+
]
|
56
|
+
|
57
|
+
def __init__(self):
|
58
|
+
"""Initialize the fix detection hook."""
|
59
|
+
super().__init__(
|
60
|
+
name="fix_detection",
|
61
|
+
priority=87, # After failure detection, before learning extraction
|
62
|
+
)
|
63
|
+
self.tracker = get_failure_tracker()
|
64
|
+
|
65
|
+
def execute(self, context: HookContext) -> HookResult:
|
66
|
+
"""Execute fix detection on successful tool execution.
|
67
|
+
|
68
|
+
WHY: When a tool succeeds, check if it fixes a previous failure.
|
69
|
+
This creates failure-fix pairs that can be used for learning extraction.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
context: Hook context containing tool execution data
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
HookResult with detection results
|
76
|
+
"""
|
77
|
+
try:
|
78
|
+
# Extract tool execution data
|
79
|
+
tool_name = context.data.get("tool_name")
|
80
|
+
exit_code = context.data.get("exit_code", 0)
|
81
|
+
|
82
|
+
# Only process monitored tools
|
83
|
+
if tool_name not in self.MONITORED_TOOLS:
|
84
|
+
return HookResult(success=True, modified=False)
|
85
|
+
|
86
|
+
# Only process successful executions
|
87
|
+
if exit_code != 0:
|
88
|
+
return HookResult(success=True, modified=False)
|
89
|
+
|
90
|
+
# Check if there are any unfixed failures to potentially match
|
91
|
+
unfixed_failures = self.tracker.get_unfixed_failures()
|
92
|
+
if not unfixed_failures:
|
93
|
+
# No failures to fix, skip
|
94
|
+
return HookResult(success=True, modified=False)
|
95
|
+
|
96
|
+
# Attempt to detect a fix
|
97
|
+
tool_output = self._extract_tool_output(context.data)
|
98
|
+
fix_context = self._build_fix_context(context)
|
99
|
+
|
100
|
+
fix_result = self.tracker.detect_fix(
|
101
|
+
tool_name=tool_name,
|
102
|
+
tool_output=tool_output,
|
103
|
+
exit_code=exit_code,
|
104
|
+
context=fix_context,
|
105
|
+
)
|
106
|
+
|
107
|
+
if fix_result:
|
108
|
+
fix_event, failure_event = fix_result
|
109
|
+
logger.info(
|
110
|
+
f"Fix detected: {failure_event.task_type} - "
|
111
|
+
f"resolved failure {failure_event.task_id}"
|
112
|
+
)
|
113
|
+
|
114
|
+
# Store fix info in metadata for learning extraction hook
|
115
|
+
return HookResult(
|
116
|
+
success=True,
|
117
|
+
modified=False,
|
118
|
+
metadata={
|
119
|
+
"fix_detected": True,
|
120
|
+
"failure_id": failure_event.task_id,
|
121
|
+
"fix_event": fix_event,
|
122
|
+
"failure_event": failure_event,
|
123
|
+
},
|
124
|
+
)
|
125
|
+
|
126
|
+
return HookResult(success=True, modified=False)
|
127
|
+
|
128
|
+
except Exception as e:
|
129
|
+
logger.error(f"Error in fix detection hook: {e}", exc_info=True)
|
130
|
+
return HookResult(success=False, error=str(e), modified=False)
|
131
|
+
|
132
|
+
def validate(self, context: HookContext) -> bool:
|
133
|
+
"""Validate if this hook should run for the given context.
|
134
|
+
|
135
|
+
Args:
|
136
|
+
context: Hook context to validate
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
True if hook should execute
|
140
|
+
"""
|
141
|
+
if not super().validate(context):
|
142
|
+
return False
|
143
|
+
|
144
|
+
# Run for POST_DELEGATION events (after tool execution)
|
145
|
+
if context.hook_type != HookType.POST_DELEGATION:
|
146
|
+
return False
|
147
|
+
|
148
|
+
# Must have tool execution data
|
149
|
+
return "tool_name" in context.data
|
150
|
+
|
151
|
+
def _extract_tool_output(self, data: Dict[str, Any]) -> str:
|
152
|
+
"""Extract tool output from event data.
|
153
|
+
|
154
|
+
Args:
|
155
|
+
data: Event data dictionary
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
Tool output string
|
159
|
+
"""
|
160
|
+
# Try various output fields
|
161
|
+
output = data.get("output") or data.get("result") or data.get("stdout") or ""
|
162
|
+
|
163
|
+
# Handle nested result structures
|
164
|
+
if isinstance(output, dict):
|
165
|
+
output = output.get("output") or output.get("content") or str(output)
|
166
|
+
|
167
|
+
return str(output) if output else ""
|
168
|
+
|
169
|
+
def _build_fix_context(self, context: HookContext) -> Dict[str, str]:
|
170
|
+
"""Build context dictionary for fix event.
|
171
|
+
|
172
|
+
Args:
|
173
|
+
context: Hook context
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
Context dictionary with agent, session, and other info
|
177
|
+
"""
|
178
|
+
fix_context = {}
|
179
|
+
|
180
|
+
# Extract relevant context fields
|
181
|
+
if context.session_id:
|
182
|
+
fix_context["session_id"] = context.session_id
|
183
|
+
|
184
|
+
# Extract agent info
|
185
|
+
agent_type = (
|
186
|
+
context.data.get("agent_type")
|
187
|
+
or context.data.get("subagent_type")
|
188
|
+
or context.metadata.get("agent_type")
|
189
|
+
)
|
190
|
+
if agent_type:
|
191
|
+
fix_context["agent_type"] = agent_type
|
192
|
+
|
193
|
+
# Extract command/script info for Bash tool
|
194
|
+
if context.data.get("tool_name") == "Bash":
|
195
|
+
command = context.data.get("command") or context.data.get(
|
196
|
+
"tool_input", {}
|
197
|
+
).get("command")
|
198
|
+
if command:
|
199
|
+
fix_context["command"] = command
|
200
|
+
|
201
|
+
# Extract working directory
|
202
|
+
working_dir = context.data.get("working_directory") or context.data.get("cwd")
|
203
|
+
if working_dir:
|
204
|
+
fix_context["working_dir"] = working_dir
|
205
|
+
|
206
|
+
return fix_context
|
207
|
+
|
208
|
+
|
209
|
+
def get_fix_detection_hook() -> FixDetectionHook:
|
210
|
+
"""Factory function to create fix detection hook.
|
211
|
+
|
212
|
+
WHY: Provides consistent hook creation pattern used throughout the framework.
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
Configured FixDetectionHook instance
|
216
|
+
"""
|
217
|
+
return FixDetectionHook()
|