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,286 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Learning Extraction Hook
4
+ =========================
5
+
6
+ Synthesizes learnings from failure-fix pairs and writes them to agent memory.
7
+
8
+ WHY: The final step in the failure-learning cycle is extracting actionable
9
+ learnings and persisting them to agent memory files. This hook completes the
10
+ cycle by taking failure-fix pairs and creating formatted learning entries.
11
+
12
+ DESIGN DECISION: This hook runs last (priority=89) after both failure and fix
13
+ detection. It uses template-based synthesis for MVP (no AI) and integrates
14
+ with AgentMemoryManager to write learnings to the appropriate memory files.
15
+
16
+ Integration points:
17
+ - Monitors for fix_detected metadata from FixDetectionHook
18
+ - Extracts learnings using FailureTracker
19
+ - Formats learnings as markdown
20
+ - Writes to agent memory files via AgentMemoryManager
21
+ """
22
+
23
+ import logging
24
+ from typing import Any
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 LearningExtractionHook(BaseHook):
38
+ """Hook that extracts and persists learnings from failure-fix pairs.
39
+
40
+ WHY: Automatically converts failure-fix pairs into persistent learnings
41
+ stored in agent memory files. This completes the failure-learning cycle
42
+ without requiring manual intervention.
43
+
44
+ DESIGN DECISION: Priority 89 ensures this runs last in the chain:
45
+ 1. Failure detection (85) - detects failures
46
+ 2. Fix detection (87) - matches fixes with failures
47
+ 3. Learning extraction (89) - creates and persists learnings
48
+
49
+ MVP uses template-based learning synthesis. Future versions could use
50
+ AI to analyze git diffs, code changes, and generate richer learnings.
51
+ """
52
+
53
+ def __init__(self):
54
+ """Initialize the learning extraction hook."""
55
+ super().__init__(
56
+ name="learning_extraction",
57
+ priority=89, # Last in the chain, after fix detection
58
+ )
59
+ self.tracker = get_failure_tracker()
60
+ self._memory_manager = None
61
+
62
+ @property
63
+ def memory_manager(self):
64
+ """Lazy-load memory manager to avoid circular imports.
65
+
66
+ WHY: AgentMemoryManager may import hooks, so we lazy-load to prevent
67
+ circular dependency issues.
68
+
69
+ Returns:
70
+ AgentMemoryManager instance
71
+ """
72
+ if self._memory_manager is None:
73
+ try:
74
+ from claude_mpm.services.agents.memory.agent_memory_manager import (
75
+ get_memory_manager,
76
+ )
77
+
78
+ self._memory_manager = get_memory_manager()
79
+ except ImportError as e:
80
+ logger.error(f"Failed to import AgentMemoryManager: {e}")
81
+ raise
82
+
83
+ return self._memory_manager
84
+
85
+ def execute(self, context: HookContext) -> HookResult:
86
+ """Execute learning extraction from failure-fix pairs.
87
+
88
+ WHY: When a fix is detected, we have everything needed to extract a
89
+ learning: the original failure, the fix that resolved it, and the
90
+ context. This method synthesizes a learning and writes it to memory.
91
+
92
+ Args:
93
+ context: Hook context containing fix detection metadata
94
+
95
+ Returns:
96
+ HookResult with extraction results
97
+ """
98
+ try:
99
+ # Check if this is a fix detection event
100
+ metadata = context.metadata or {}
101
+ if not metadata.get("fix_detected"):
102
+ # Not a fix event, skip
103
+ return HookResult(success=True, modified=False)
104
+
105
+ # Extract failure and fix events from metadata
106
+ failure_event = metadata.get("failure_event")
107
+ fix_event = metadata.get("fix_event")
108
+
109
+ if not failure_event or not fix_event:
110
+ logger.warning("Fix detected but failure/fix events not in metadata")
111
+ return HookResult(success=True, modified=False)
112
+
113
+ # Extract learning from failure-fix pair
114
+ learning = self.tracker.extract_learning(
115
+ fix_event=fix_event,
116
+ failure_event=failure_event,
117
+ target_agent=self._determine_target_agent(context, failure_event),
118
+ )
119
+
120
+ # Format learning as markdown
121
+ learning_markdown = learning.to_markdown()
122
+
123
+ # Write to agent memory
124
+ success = self._write_to_memory(
125
+ agent_id=learning.target_agent, learning_text=learning_markdown
126
+ )
127
+
128
+ if success:
129
+ logger.info(
130
+ f"Learning extracted and saved for {learning.target_agent}: "
131
+ f"{learning.category}"
132
+ )
133
+ return HookResult(
134
+ success=True,
135
+ modified=False,
136
+ metadata={
137
+ "learning_extracted": True,
138
+ "target_agent": learning.target_agent,
139
+ "learning_category": learning.category,
140
+ },
141
+ )
142
+ logger.warning(
143
+ f"Failed to write learning to memory for {learning.target_agent}"
144
+ )
145
+ return HookResult(
146
+ success=True, # Don't fail the hook, just log warning
147
+ modified=False,
148
+ metadata={"learning_extracted": False},
149
+ )
150
+
151
+ except Exception as e:
152
+ logger.error(f"Error in learning extraction hook: {e}", exc_info=True)
153
+ return HookResult(success=False, error=str(e), modified=False)
154
+
155
+ def validate(self, context: HookContext) -> bool:
156
+ """Validate if this hook should run for the given context.
157
+
158
+ Args:
159
+ context: Hook context to validate
160
+
161
+ Returns:
162
+ True if hook should execute
163
+ """
164
+ if not super().validate(context):
165
+ return False
166
+
167
+ # Run for POST_DELEGATION events (after tool execution)
168
+ if context.hook_type != HookType.POST_DELEGATION:
169
+ return False
170
+
171
+ # Must have fix detection metadata
172
+ metadata = context.metadata or {}
173
+ return metadata.get("fix_detected", False)
174
+
175
+ def _determine_target_agent(self, context: HookContext, failure_event: Any) -> str:
176
+ """Determine which agent should receive the learning.
177
+
178
+ WHY: Learnings should go to the agent most likely to benefit from them.
179
+ This method applies routing logic to determine the best target.
180
+
181
+ Args:
182
+ context: Hook context
183
+ failure_event: The failure event
184
+
185
+ Returns:
186
+ Agent identifier (PM, engineer, qa, etc.)
187
+ """
188
+ # Try to get agent from context first
189
+ agent_type = (
190
+ context.data.get("agent_type")
191
+ or context.data.get("subagent_type")
192
+ or context.metadata.get("agent_type")
193
+ )
194
+ if agent_type:
195
+ return agent_type
196
+
197
+ # Check failure event context
198
+ if hasattr(failure_event, "context") and failure_event.context.get(
199
+ "agent_type"
200
+ ):
201
+ return failure_event.context["agent_type"]
202
+
203
+ # Fall back to task-based routing
204
+ if hasattr(failure_event, "task_type"):
205
+ task_type = failure_event.task_type
206
+ if task_type in ("test", "lint"):
207
+ return "qa"
208
+ if task_type in ("build", "install", "script") or task_type == "git":
209
+ return "engineer"
210
+
211
+ # Default to PM
212
+ return "PM"
213
+
214
+ def _write_to_memory(self, agent_id: str, learning_text: str) -> bool:
215
+ """Write learning to agent memory file.
216
+
217
+ WHY: Learnings must be persisted to memory files so agents can access
218
+ them in future sessions. This method uses AgentMemoryManager to handle
219
+ the actual file operations.
220
+
221
+ Args:
222
+ agent_id: Agent identifier
223
+ learning_text: Markdown-formatted learning
224
+
225
+ Returns:
226
+ True if write succeeded, False otherwise
227
+ """
228
+ try:
229
+ # Parse learning sections from markdown
230
+ learning_items = self._parse_learning_markdown(learning_text)
231
+
232
+ if not learning_items:
233
+ logger.warning(f"No learning items parsed from: {learning_text}")
234
+ return False
235
+
236
+ # Add to agent memory
237
+ return self.memory_manager.update_agent_memory(
238
+ agent_id=agent_id, new_items=learning_items
239
+ )
240
+
241
+ except Exception as e:
242
+ logger.error(f"Failed to write learning to memory for {agent_id}: {e}")
243
+ return False
244
+
245
+ def _parse_learning_markdown(self, learning_markdown: str) -> list:
246
+ """Parse learning markdown into list items for memory.
247
+
248
+ WHY: AgentMemoryManager expects a list of learning items. We need to
249
+ convert the markdown-formatted learning into individual list items.
250
+
251
+ Args:
252
+ learning_markdown: Markdown-formatted learning
253
+
254
+ Returns:
255
+ List of learning items
256
+ """
257
+ items = []
258
+
259
+ # Split by lines and extract bullet points
260
+ lines = learning_markdown.split("\n")
261
+
262
+ for line in lines:
263
+ line = line.strip()
264
+ if line.startswith("- **"):
265
+ # This is a learning item (e.g., "- **Problem**: ...")
266
+ items.append(line)
267
+ elif line.startswith("## "):
268
+ # This is a category header, skip it
269
+ continue
270
+
271
+ # If no items found, return the whole thing as a single item
272
+ if not items:
273
+ items = [f"- {learning_markdown.strip()}"]
274
+
275
+ return items
276
+
277
+
278
+ def get_learning_extraction_hook() -> LearningExtractionHook:
279
+ """Factory function to create learning extraction hook.
280
+
281
+ WHY: Provides consistent hook creation pattern used throughout the framework.
282
+
283
+ Returns:
284
+ Configured LearningExtractionHook instance
285
+ """
286
+ return LearningExtractionHook()