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,563 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Failure Tracker Service
4
+ =======================
5
+
6
+ Session-level state manager for tracking failures and their fixes to enable
7
+ automatic learning extraction.
8
+
9
+ WHY: When tasks fail and agents fix them, we need to track these failure-fix
10
+ pairs to extract learnings. This service provides in-memory session tracking
11
+ without requiring database or filesystem persistence.
12
+
13
+ DESIGN DECISION: Session-scoped tracking keeps the MVP simple. Failures are
14
+ tracked during a session, matched with fixes, and learnings are extracted
15
+ before session end. No persistent storage needed for MVP.
16
+
17
+ Architecture:
18
+ - Failure events: Captured from tool outputs (errors, exceptions, test failures)
19
+ - Fix events: Detected when same task type succeeds after failure
20
+ - Learning synthesis: Template-based extraction from failure-fix pairs
21
+ - Memory routing: Direct learnings to appropriate agent memory files
22
+
23
+ Example flow:
24
+ 1. Bash tool returns error → FailureEvent created
25
+ 2. User or agent makes changes
26
+ 3. Bash tool succeeds → Fix detected, matched with failure
27
+ 4. Learning extracted and written to agent memory
28
+ """
29
+
30
+ import logging
31
+ import re
32
+ from dataclasses import dataclass, field
33
+ from datetime import datetime, timezone
34
+ from typing import Dict, List, Optional, Tuple
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ @dataclass
40
+ class FailureEvent:
41
+ """Represents a detected task failure.
42
+
43
+ Attributes:
44
+ task_id: Unique identifier for this failure event
45
+ task_type: Type of task that failed (bash, test, build, etc.)
46
+ tool_name: Name of tool that failed (Bash, NotebookEdit, etc.)
47
+ error_message: The actual error message
48
+ context: Additional context (agent, session, working_dir, etc.)
49
+ timestamp: When the failure occurred
50
+ fixed: Whether this failure has been fixed
51
+ fix_timestamp: When the fix occurred (if fixed)
52
+ """
53
+
54
+ task_id: str
55
+ task_type: str
56
+ tool_name: str
57
+ error_message: str
58
+ context: Dict[str, str] = field(default_factory=dict)
59
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
60
+ fixed: bool = False
61
+ fix_timestamp: Optional[datetime] = None
62
+
63
+ def mark_fixed(self) -> None:
64
+ """Mark this failure as fixed."""
65
+ self.fixed = True
66
+ self.fix_timestamp = datetime.now(timezone.utc)
67
+
68
+
69
+ @dataclass
70
+ class FixEvent:
71
+ """Represents a detected fix for a previous failure.
72
+
73
+ Attributes:
74
+ task_type: Type of task that succeeded
75
+ tool_name: Name of tool that succeeded
76
+ success_message: Output from successful execution
77
+ context: Additional context
78
+ timestamp: When the fix occurred
79
+ matched_failure: The failure event this fix resolves
80
+ """
81
+
82
+ task_type: str
83
+ tool_name: str
84
+ success_message: str
85
+ context: Dict[str, str] = field(default_factory=dict)
86
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
87
+ matched_failure: Optional[FailureEvent] = None
88
+
89
+
90
+ @dataclass
91
+ class Learning:
92
+ """Represents an extracted learning from a failure-fix pair.
93
+
94
+ Attributes:
95
+ category: Learning category (error-handling, testing, configuration, etc.)
96
+ problem: Description of the original problem
97
+ solution: Description of the solution
98
+ context: Task context (tool, agent, etc.)
99
+ target_agent: Which agent should receive this learning
100
+ failure_event: The original failure
101
+ fix_event: The fix that resolved it
102
+ timestamp: When the learning was extracted
103
+ """
104
+
105
+ category: str
106
+ problem: str
107
+ solution: str
108
+ context: Dict[str, str]
109
+ target_agent: str
110
+ failure_event: FailureEvent
111
+ fix_event: FixEvent
112
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
113
+
114
+ def to_markdown(self) -> str:
115
+ """Format learning as markdown for memory file.
116
+
117
+ Returns:
118
+ Markdown-formatted learning entry
119
+ """
120
+ return (
121
+ f"## {self.category}\n"
122
+ f"- **Problem**: {self.problem}\n"
123
+ f"- **Solution**: {self.solution}\n"
124
+ f"- **Context**: {', '.join(f'{k}: {v}' for k, v in self.context.items())}\n"
125
+ f"- **Date**: {self.timestamp.strftime('%Y-%m-%d')}\n"
126
+ )
127
+
128
+
129
+ class FailureTracker:
130
+ """Session-level tracker for failures, fixes, and learnings.
131
+
132
+ WHY: Provides centralized state management for the failure-learning system.
133
+ Hooks interact with this tracker to record failures, detect fixes, and
134
+ extract learnings.
135
+
136
+ DESIGN DECISION: In-memory session tracking is sufficient for MVP. Each
137
+ session maintains its own failure history. When a fix is detected, we
138
+ search for matching failures and create learning pairs.
139
+ """
140
+
141
+ # Failure detection patterns (ordered from most specific to least specific)
142
+ ERROR_PATTERNS = [
143
+ (r"SyntaxError: (.+)", "syntax-error"),
144
+ (r"TypeError: (.+)", "type-error"),
145
+ (r"ImportError: (.+)", "import-error"),
146
+ (r"ModuleNotFoundError: (.+)", "module-not-found"),
147
+ (r"FileNotFoundError: (.+)", "file-not-found"),
148
+ (r"FAILED (.+)", "test-failure"),
149
+ (r"✗ (.+) failed", "test-failure"),
150
+ (r"(\d+) failed", "test-failure"),
151
+ (r"Exception: (.+)", "exception"),
152
+ (r"Command failed: (.+)", "command-error"),
153
+ (r"Error: (.+)", "error"), # Generic error - match last
154
+ ]
155
+
156
+ # Task type classification patterns
157
+ TASK_TYPE_PATTERNS = {
158
+ "test": [r"pytest", r"test", r"\.test\.py", r"tests/"],
159
+ "build": [r"make", r"build", r"compile", r"setup\.py"],
160
+ "lint": [r"lint", r"flake8", r"mypy", r"black", r"isort", r"ruff"],
161
+ "git": [r"git ", r"commit", r"push", r"pull", r"merge"],
162
+ "install": [r"pip install", r"npm install", r"yarn", r"poetry"],
163
+ "script": [r"\.sh", r"\.py", r"\.js", r"script"],
164
+ }
165
+
166
+ def __init__(self):
167
+ """Initialize the failure tracker."""
168
+ self.failures: List[FailureEvent] = []
169
+ self.fixes: List[FixEvent] = []
170
+ self.learnings: List[Learning] = []
171
+ self.session_id = datetime.now(timezone.utc).isoformat()
172
+
173
+ def detect_failure(
174
+ self, tool_name: str, tool_output: str, context: Optional[Dict[str, str]] = None
175
+ ) -> Optional[FailureEvent]:
176
+ """Detect if tool output contains a failure.
177
+
178
+ WHY: Failures can occur in many forms (errors, exceptions, test failures).
179
+ This method uses regex patterns to identify failures and extract relevant
180
+ error messages.
181
+
182
+ Args:
183
+ tool_name: Name of the tool that executed
184
+ tool_output: Output from the tool
185
+ context: Additional context (agent, session, etc.)
186
+
187
+ Returns:
188
+ FailureEvent if failure detected, None otherwise
189
+ """
190
+ if not tool_output:
191
+ return None
192
+
193
+ context = context or {}
194
+
195
+ # Check each error pattern
196
+ for pattern, error_type in self.ERROR_PATTERNS:
197
+ match = re.search(pattern, tool_output, re.MULTILINE | re.IGNORECASE)
198
+ if match:
199
+ error_message = match.group(1) if match.lastindex else match.group(0)
200
+
201
+ # Classify task type
202
+ task_type = self._classify_task_type(tool_output, context)
203
+
204
+ # Create failure event
205
+ task_id = f"{task_type}_{len(self.failures)}_{int(datetime.now(timezone.utc).timestamp())}"
206
+ failure = FailureEvent(
207
+ task_id=task_id,
208
+ task_type=task_type,
209
+ tool_name=tool_name,
210
+ error_message=error_message.strip(),
211
+ context={
212
+ **context,
213
+ "error_type": error_type,
214
+ "output_preview": tool_output[:200],
215
+ },
216
+ )
217
+
218
+ self.failures.append(failure)
219
+ logger.info(f"Detected failure: {task_type} - {error_message[:50]}...")
220
+ return failure
221
+
222
+ return None
223
+
224
+ def detect_fix(
225
+ self,
226
+ tool_name: str,
227
+ tool_output: str,
228
+ exit_code: int = 0,
229
+ context: Optional[Dict[str, str]] = None,
230
+ ) -> Optional[Tuple[FixEvent, FailureEvent]]:
231
+ """Detect if a successful execution fixes a previous failure.
232
+
233
+ WHY: When a task succeeds, it might be fixing a previous failure of the
234
+ same task type. This method matches successful executions with recent
235
+ failures to detect fixes.
236
+
237
+ Args:
238
+ tool_name: Name of the tool that succeeded
239
+ tool_output: Output from the tool
240
+ exit_code: Exit code (0 = success)
241
+ context: Additional context
242
+
243
+ Returns:
244
+ Tuple of (FixEvent, matched FailureEvent) if fix detected, None otherwise
245
+ """
246
+ # Only consider successful executions
247
+ if exit_code != 0:
248
+ return None
249
+
250
+ context = context or {}
251
+ task_type = self._classify_task_type(tool_output, context)
252
+
253
+ # Find matching unfixed failure
254
+ matching_failure = self._find_matching_failure(task_type, tool_name)
255
+ if not matching_failure:
256
+ return None
257
+
258
+ # Create fix event
259
+ fix = FixEvent(
260
+ task_type=task_type,
261
+ tool_name=tool_name,
262
+ success_message=tool_output[:200] if tool_output else "Success",
263
+ context=context,
264
+ matched_failure=matching_failure,
265
+ )
266
+
267
+ # Mark failure as fixed
268
+ matching_failure.mark_fixed()
269
+ self.fixes.append(fix)
270
+
271
+ logger.info(f"Detected fix for {task_type} failure: {matching_failure.task_id}")
272
+ return (fix, matching_failure)
273
+
274
+ def extract_learning(
275
+ self,
276
+ fix_event: FixEvent,
277
+ failure_event: FailureEvent,
278
+ target_agent: Optional[str] = None,
279
+ ) -> Learning:
280
+ """Extract learning from a failure-fix pair.
281
+
282
+ WHY: When we have a failure and its fix, we can synthesize a learning
283
+ that captures the problem-solution pair for future reference.
284
+
285
+ DESIGN DECISION: MVP uses template-based extraction (no AI). The learning
286
+ format is simple and actionable, ready for agent memory files.
287
+
288
+ Args:
289
+ fix_event: The fix that resolved the failure
290
+ failure_event: The original failure
291
+ target_agent: Which agent should receive this learning (auto-detected if None)
292
+
293
+ Returns:
294
+ Learning object with extracted knowledge
295
+ """
296
+ # Auto-detect target agent if not specified
297
+ if not target_agent:
298
+ target_agent = self._determine_target_agent(failure_event, fix_event)
299
+
300
+ # Categorize the learning
301
+ category = self._categorize_learning(failure_event)
302
+
303
+ # Extract problem and solution descriptions
304
+ problem = self._extract_problem_description(failure_event)
305
+ solution = self._extract_solution_description(fix_event, failure_event)
306
+
307
+ # Build context
308
+ learning_context = {
309
+ "task_type": failure_event.task_type,
310
+ "tool": failure_event.tool_name,
311
+ "error_type": failure_event.context.get("error_type", "unknown"),
312
+ }
313
+
314
+ # Add agent context if available
315
+ if "agent_type" in failure_event.context:
316
+ learning_context["agent"] = failure_event.context["agent_type"]
317
+
318
+ learning = Learning(
319
+ category=category,
320
+ problem=problem,
321
+ solution=solution,
322
+ context=learning_context,
323
+ target_agent=target_agent,
324
+ failure_event=failure_event,
325
+ fix_event=fix_event,
326
+ )
327
+
328
+ self.learnings.append(learning)
329
+ logger.info(f"Extracted learning for {target_agent}: {category}")
330
+ return learning
331
+
332
+ def get_unfixed_failures(self) -> List[FailureEvent]:
333
+ """Get all failures that haven't been fixed yet.
334
+
335
+ Returns:
336
+ List of unfixed failure events
337
+ """
338
+ return [f for f in self.failures if not f.fixed]
339
+
340
+ def get_learnings_for_agent(self, agent_id: str) -> List[Learning]:
341
+ """Get all learnings targeted for a specific agent.
342
+
343
+ Args:
344
+ agent_id: Agent identifier
345
+
346
+ Returns:
347
+ List of learnings for that agent
348
+ """
349
+ return [l for l in self.learnings if l.target_agent == agent_id]
350
+
351
+ def get_session_stats(self) -> Dict[str, int]:
352
+ """Get statistics for the current session.
353
+
354
+ Returns:
355
+ Dict with failure/fix/learning counts
356
+ """
357
+ return {
358
+ "total_failures": len(self.failures),
359
+ "fixed_failures": len([f for f in self.failures if f.fixed]),
360
+ "unfixed_failures": len([f for f in self.failures if not f.fixed]),
361
+ "total_fixes": len(self.fixes),
362
+ "total_learnings": len(self.learnings),
363
+ }
364
+
365
+ def _classify_task_type(self, output: str, context: Dict[str, str]) -> str:
366
+ """Classify the task type based on output and context.
367
+
368
+ Args:
369
+ output: Tool output
370
+ context: Additional context
371
+
372
+ Returns:
373
+ Task type string (test, build, lint, etc.)
374
+ """
375
+ # Check context first
376
+ if "command" in context:
377
+ command = context["command"].lower()
378
+ for task_type, patterns in self.TASK_TYPE_PATTERNS.items():
379
+ if any(
380
+ re.search(pattern, command, re.IGNORECASE) for pattern in patterns
381
+ ):
382
+ return task_type
383
+
384
+ # Check output
385
+ output_lower = output.lower()
386
+ for task_type, patterns in self.TASK_TYPE_PATTERNS.items():
387
+ if any(
388
+ re.search(pattern, output_lower, re.IGNORECASE) for pattern in patterns
389
+ ):
390
+ return task_type
391
+
392
+ # Default to general execution
393
+ return "execution"
394
+
395
+ def _find_matching_failure(
396
+ self, task_type: str, tool_name: str
397
+ ) -> Optional[FailureEvent]:
398
+ """Find the most recent unfixed failure matching the task type.
399
+
400
+ Args:
401
+ task_type: Type of task
402
+ tool_name: Tool name
403
+
404
+ Returns:
405
+ Matching FailureEvent or None
406
+ """
407
+ # Search in reverse chronological order
408
+ for failure in reversed(self.failures):
409
+ if (
410
+ not failure.fixed
411
+ and failure.task_type == task_type
412
+ and failure.tool_name == tool_name
413
+ ):
414
+ return failure
415
+
416
+ # If no exact match, try matching just tool_name for generic tasks
417
+ if task_type == "execution":
418
+ for failure in reversed(self.failures):
419
+ if not failure.fixed and failure.tool_name == tool_name:
420
+ return failure
421
+
422
+ return None
423
+
424
+ def _determine_target_agent(
425
+ self, failure_event: FailureEvent, fix_event: FixEvent
426
+ ) -> str:
427
+ """Determine which agent should receive the learning.
428
+
429
+ Args:
430
+ failure_event: The failure
431
+ fix_event: The fix
432
+
433
+ Returns:
434
+ Agent identifier (PM, engineer, qa, etc.)
435
+ """
436
+ # Check if agent was involved in the context
437
+ if "agent_type" in failure_event.context:
438
+ return failure_event.context["agent_type"]
439
+
440
+ if "agent_type" in fix_event.context:
441
+ return fix_event.context["agent_type"]
442
+
443
+ # Route by task type
444
+ task_type = failure_event.task_type
445
+ if task_type in ("test", "lint"):
446
+ return "qa"
447
+ if task_type in ("build", "install", "script") or task_type == "git":
448
+ return "engineer"
449
+ # Default to PM for general learnings
450
+ return "PM"
451
+
452
+ def _categorize_learning(self, failure_event: FailureEvent) -> str:
453
+ """Categorize the learning based on failure type.
454
+
455
+ Args:
456
+ failure_event: The failure event
457
+
458
+ Returns:
459
+ Category string
460
+ """
461
+ error_type = failure_event.context.get("error_type", "unknown")
462
+ task_type = failure_event.task_type
463
+
464
+ # Map error types to categories
465
+ category_map = {
466
+ "test-failure": "Testing",
467
+ "syntax-error": "Code Quality",
468
+ "type-error": "Code Quality",
469
+ "import-error": "Dependencies",
470
+ "module-not-found": "Dependencies",
471
+ "file-not-found": "File Management",
472
+ "command-error": "Configuration",
473
+ "error": "Error Handling",
474
+ "exception": "Error Handling",
475
+ }
476
+
477
+ # Try error type first
478
+ if error_type in category_map:
479
+ return category_map[error_type]
480
+
481
+ # Try task type
482
+ task_category_map = {
483
+ "test": "Testing",
484
+ "build": "Build Process",
485
+ "lint": "Code Quality",
486
+ "git": "Version Control",
487
+ "install": "Dependencies",
488
+ }
489
+
490
+ if task_type in task_category_map:
491
+ return task_category_map[task_type]
492
+
493
+ return "General"
494
+
495
+ def _extract_problem_description(self, failure_event: FailureEvent) -> str:
496
+ """Extract a concise problem description from the failure.
497
+
498
+ Args:
499
+ failure_event: The failure event
500
+
501
+ Returns:
502
+ Problem description string
503
+ """
504
+ error_msg = failure_event.error_message
505
+ task_type = failure_event.task_type
506
+
507
+ # Truncate long error messages
508
+ if len(error_msg) > 100:
509
+ error_msg = error_msg[:97] + "..."
510
+
511
+ return f"{task_type.capitalize()} failed: {error_msg}"
512
+
513
+ def _extract_solution_description(
514
+ self, fix_event: FixEvent, failure_event: FailureEvent
515
+ ) -> str:
516
+ """Extract a solution description from the fix.
517
+
518
+ WHY: We want to capture what changed between failure and fix.
519
+ For MVP, we use a simple heuristic based on the time gap.
520
+
521
+ Args:
522
+ fix_event: The fix event
523
+ failure_event: The failure event
524
+
525
+ Returns:
526
+ Solution description string
527
+ """
528
+ # Calculate time between failure and fix
529
+ time_delta = fix_event.timestamp - failure_event.timestamp
530
+ time_str = f"{int(time_delta.total_seconds())}s"
531
+
532
+ # Generic solution description for MVP
533
+ # In future versions, this could analyze git diff, file changes, etc.
534
+ return f"Fixed after {time_str} - verified with successful {fix_event.task_type} execution"
535
+
536
+
537
+ # Singleton instance for session-level tracking
538
+ _tracker_instance: Optional[FailureTracker] = None
539
+
540
+
541
+ def get_failure_tracker() -> FailureTracker:
542
+ """Get or create the singleton FailureTracker instance.
543
+
544
+ WHY: Session-level tracking requires a singleton to maintain state
545
+ across multiple hook invocations during the same session.
546
+
547
+ Returns:
548
+ The FailureTracker singleton instance
549
+ """
550
+ global _tracker_instance
551
+ if _tracker_instance is None:
552
+ _tracker_instance = FailureTracker()
553
+ return _tracker_instance
554
+
555
+
556
+ def reset_failure_tracker() -> None:
557
+ """Reset the failure tracker (for testing or session restart).
558
+
559
+ WHY: Tests need to reset state between runs. Also useful for
560
+ explicitly starting a new tracking session.
561
+ """
562
+ global _tracker_instance
563
+ _tracker_instance = None
@@ -95,6 +95,9 @@ class MemoryHookService(BaseService, MemoryHookInterface):
95
95
  # Register kuzu-memory hooks if available
96
96
  self._register_kuzu_memory_hooks()
97
97
 
98
+ # Register failure-learning hooks
99
+ self._register_failure_learning_hooks()
100
+
98
101
  except Exception as e:
99
102
  self.logger.warning(f"Failed to register memory hooks: {e}")
100
103
 
@@ -177,6 +180,79 @@ class MemoryHookService(BaseService, MemoryHookInterface):
177
180
  except Exception as e:
178
181
  self.logger.warning(f"Failed to register kuzu-memory hooks: {e}")
179
182
 
183
+ def _register_failure_learning_hooks(self):
184
+ """Register failure-learning hooks for automatic learning extraction.
185
+
186
+ WHY: When tasks fail and agents fix them, we want to automatically capture
187
+ this as a learning. The failure-learning system provides:
188
+ 1. Failure detection from tool outputs (errors, exceptions, test failures)
189
+ 2. Fix detection when same task type succeeds after failure
190
+ 3. Learning extraction and persistence to agent memory files
191
+
192
+ DESIGN DECISION: These hooks work as a chain with specific priorities:
193
+ - FailureDetectionHook (priority=85): Detects failures after tool execution
194
+ - FixDetectionHook (priority=87): Matches fixes with failures
195
+ - LearningExtractionHook (priority=89): Extracts and persists learnings
196
+
197
+ The system is enabled by default but can be disabled via configuration.
198
+ """
199
+ try:
200
+ # Check if failure-learning is enabled in config
201
+ from claude_mpm.core.config import Config
202
+
203
+ config = Config()
204
+ failure_learning_config = config.get("memory.failure_learning", {})
205
+
206
+ if isinstance(failure_learning_config, dict):
207
+ enabled = failure_learning_config.get("enabled", True)
208
+ else:
209
+ # Default to enabled if config section doesn't exist
210
+ enabled = True
211
+
212
+ if not enabled:
213
+ self.logger.debug("Failure-learning disabled in configuration")
214
+ return
215
+
216
+ # Import failure-learning hooks
217
+ from claude_mpm.hooks.failure_learning import (
218
+ get_failure_detection_hook,
219
+ get_fix_detection_hook,
220
+ get_learning_extraction_hook,
221
+ )
222
+
223
+ # Get hook instances
224
+ failure_hook = get_failure_detection_hook()
225
+ fix_hook = get_fix_detection_hook()
226
+ learning_hook = get_learning_extraction_hook()
227
+
228
+ # Register hooks in priority order
229
+ success1 = self.hook_service.register_hook(failure_hook)
230
+ success2 = self.hook_service.register_hook(fix_hook)
231
+ success3 = self.hook_service.register_hook(learning_hook)
232
+
233
+ if success1:
234
+ self.registered_hooks.append("failure_detection")
235
+ self.logger.debug("✅ Failure detection enabled")
236
+
237
+ if success2:
238
+ self.registered_hooks.append("fix_detection")
239
+ self.logger.debug("✅ Fix detection enabled")
240
+
241
+ if success3:
242
+ self.registered_hooks.append("learning_extraction")
243
+ self.logger.debug("✅ Learning extraction enabled")
244
+
245
+ if success1 and success2 and success3:
246
+ self.logger.info(
247
+ "✅ Failure-learning system enabled "
248
+ "(failures → fixes → learnings → memory)"
249
+ )
250
+
251
+ except ImportError as e:
252
+ self.logger.debug(f"Failure-learning hooks not available: {e}")
253
+ except Exception as e:
254
+ self.logger.warning(f"Failed to register failure-learning hooks: {e}")
255
+
180
256
  def _load_relevant_memories_hook(self, context):
181
257
  """Hook function to load relevant memories before Claude interaction.
182
258
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 4.8.0
3
+ Version: 4.8.3
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team