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.
Files changed (24) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/golang_engineer.json +257 -0
  3. claude_mpm/agents/templates/nextjs_engineer.json +122 -132
  4. claude_mpm/agents/templates/php-engineer.json +258 -175
  5. claude_mpm/agents/templates/product_owner.json +335 -0
  6. claude_mpm/agents/templates/python_engineer.json +150 -80
  7. claude_mpm/agents/templates/ruby-engineer.json +115 -191
  8. claude_mpm/agents/templates/rust_engineer.json +257 -0
  9. claude_mpm/agents/templates/typescript_engineer.json +102 -124
  10. claude_mpm/hooks/__init__.py +14 -0
  11. claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
  12. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
  13. claude_mpm/hooks/failure_learning/__init__.py +60 -0
  14. claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
  15. claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
  16. claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
  17. claude_mpm/services/memory/failure_tracker.py +563 -0
  18. claude_mpm/services/memory_hook_service.py +76 -0
  19. {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/METADATA +1 -1
  20. {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/RECORD +24 -16
  21. {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/WHEEL +0 -0
  22. {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/entry_points.txt +0 -0
  23. {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/licenses/LICENSE +0 -0
  24. {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()