claude-mpm 5.0.2__py3-none-any.whl → 5.1.9__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (76) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +1176 -909
  4. claude_mpm/agents/base_agent_loader.py +10 -35
  5. claude_mpm/agents/frontmatter_validator.py +68 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +293 -44
  7. claude_mpm/cli/__init__.py +0 -1
  8. claude_mpm/cli/commands/__init__.py +2 -0
  9. claude_mpm/cli/commands/agent_state_manager.py +64 -11
  10. claude_mpm/cli/commands/agents.py +446 -25
  11. claude_mpm/cli/commands/auto_configure.py +535 -233
  12. claude_mpm/cli/commands/configure.py +545 -89
  13. claude_mpm/cli/commands/postmortem.py +401 -0
  14. claude_mpm/cli/commands/run.py +1 -39
  15. claude_mpm/cli/commands/skills.py +322 -19
  16. claude_mpm/cli/interactive/agent_wizard.py +302 -195
  17. claude_mpm/cli/parsers/agents_parser.py +137 -0
  18. claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
  19. claude_mpm/cli/parsers/base_parser.py +4 -0
  20. claude_mpm/cli/parsers/skills_parser.py +7 -0
  21. claude_mpm/cli/startup.py +73 -32
  22. claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
  23. claude_mpm/commands/mpm-agents-list.md +2 -2
  24. claude_mpm/commands/mpm-config-view.md +2 -2
  25. claude_mpm/commands/mpm-help.md +3 -0
  26. claude_mpm/commands/mpm-postmortem.md +123 -0
  27. claude_mpm/commands/mpm-session-resume.md +2 -2
  28. claude_mpm/commands/mpm-ticket-organize.md +2 -2
  29. claude_mpm/commands/mpm-ticket-view.md +2 -2
  30. claude_mpm/config/agent_presets.py +312 -82
  31. claude_mpm/config/skill_presets.py +392 -0
  32. claude_mpm/constants.py +1 -0
  33. claude_mpm/core/claude_runner.py +2 -25
  34. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  35. claude_mpm/core/interactive_session.py +19 -5
  36. claude_mpm/core/oneshot_session.py +16 -4
  37. claude_mpm/core/output_style_manager.py +173 -43
  38. claude_mpm/core/protocols/__init__.py +23 -0
  39. claude_mpm/core/protocols/runner_protocol.py +103 -0
  40. claude_mpm/core/protocols/session_protocol.py +131 -0
  41. claude_mpm/core/shared/singleton_manager.py +11 -4
  42. claude_mpm/core/system_context.py +38 -0
  43. claude_mpm/core/unified_agent_registry.py +129 -1
  44. claude_mpm/core/unified_config.py +22 -0
  45. claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
  46. claude_mpm/models/agent_definition.py +7 -0
  47. claude_mpm/services/agents/cache_git_manager.py +621 -0
  48. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
  49. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +195 -1
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +37 -5
  51. claude_mpm/services/analysis/__init__.py +25 -0
  52. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  53. claude_mpm/services/analysis/postmortem_service.py +765 -0
  54. claude_mpm/services/command_deployment_service.py +108 -5
  55. claude_mpm/services/core/base.py +7 -2
  56. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  57. claude_mpm/services/git/git_operations_service.py +8 -8
  58. claude_mpm/services/mcp_config_manager.py +75 -145
  59. claude_mpm/services/mcp_gateway/core/process_pool.py +22 -16
  60. claude_mpm/services/mcp_service_verifier.py +6 -3
  61. claude_mpm/services/monitor/daemon.py +28 -8
  62. claude_mpm/services/monitor/daemon_manager.py +96 -19
  63. claude_mpm/services/project/project_organizer.py +4 -0
  64. claude_mpm/services/runner_configuration_service.py +16 -3
  65. claude_mpm/services/session_management_service.py +16 -4
  66. claude_mpm/utils/agent_filters.py +288 -0
  67. claude_mpm/utils/gitignore.py +3 -0
  68. claude_mpm/utils/migration.py +372 -0
  69. claude_mpm/utils/progress.py +5 -1
  70. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/METADATA +69 -8
  71. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/RECORD +76 -62
  72. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  73. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/WHEEL +0 -0
  74. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/entry_points.txt +0 -0
  75. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/licenses/LICENSE +0 -0
  76. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/top_level.txt +0 -0
@@ -51,6 +51,63 @@ class MultiSourceAgentDeploymentService:
51
51
  self.logger = get_logger(__name__)
52
52
  self.version_manager = AgentVersionManager()
53
53
 
54
+ def _build_canonical_id_for_agent(self, agent_info: Dict[str, Any]) -> str:
55
+ """Build or retrieve canonical_id for an agent.
56
+
57
+ NEW: Supports enhanced agent matching via canonical_id.
58
+
59
+ Priority:
60
+ 1. Use existing canonical_id from agent_info if present
61
+ 2. Generate from collection_id + agent_id if available
62
+ 3. Fallback to legacy:{filename} for backward compatibility
63
+
64
+ Args:
65
+ agent_info: Agent dictionary with metadata
66
+
67
+ Returns:
68
+ Canonical ID string for matching
69
+
70
+ Example:
71
+ Remote agent: "bobmatnyc/claude-mpm-agents:pm"
72
+ Legacy agent: "legacy:custom-agent"
73
+ """
74
+ # Priority 1: Existing canonical_id
75
+ if "canonical_id" in agent_info:
76
+ return agent_info["canonical_id"]
77
+
78
+ # Priority 2: Generate from collection_id + agent_id
79
+ collection_id = agent_info.get("collection_id")
80
+ agent_id = agent_info.get("agent_id")
81
+
82
+ if collection_id and agent_id:
83
+ canonical_id = f"{collection_id}:{agent_id}"
84
+ # Cache it in agent_info for future use
85
+ agent_info["canonical_id"] = canonical_id
86
+ return canonical_id
87
+
88
+ # Priority 3: Fallback to legacy format
89
+ # Use filename or agent name
90
+ agent_name = agent_info.get("name") or agent_info.get("metadata", {}).get(
91
+ "name", "unknown"
92
+ )
93
+
94
+ # Extract filename from path
95
+ path_str = (
96
+ agent_info.get("path")
97
+ or agent_info.get("file_path")
98
+ or agent_info.get("source_file")
99
+ )
100
+
101
+ if path_str:
102
+ filename = Path(path_str).stem
103
+ canonical_id = f"legacy:{filename}"
104
+ else:
105
+ canonical_id = f"legacy:{agent_name}"
106
+
107
+ # Cache it
108
+ agent_info["canonical_id"] = canonical_id
109
+ return canonical_id
110
+
54
111
  def discover_agents_from_all_sources(
55
112
  self,
56
113
  system_templates_dir: Optional[Path] = None,
@@ -156,11 +213,19 @@ class MultiSourceAgentDeploymentService:
156
213
  agent_info["source"] = source_name
157
214
  agent_info["source_dir"] = str(source_dir)
158
215
 
216
+ # NEW: Build canonical_id for enhanced matching
217
+ canonical_id = self._build_canonical_id_for_agent(agent_info)
218
+
219
+ # Group by canonical_id (PRIMARY) for enhanced matching
220
+ # This allows matching agents from different sources with same canonical_id
221
+ # while maintaining backward compatibility with name-based matching
222
+ matching_key = canonical_id
223
+
159
224
  # Initialize list if this is the first occurrence of this agent
160
- if agent_name not in agents_by_name:
161
- agents_by_name[agent_name] = []
225
+ if matching_key not in agents_by_name:
226
+ agents_by_name[matching_key] = []
162
227
 
163
- agents_by_name[agent_name].append(agent_info)
228
+ agents_by_name[matching_key].append(agent_info)
164
229
 
165
230
  # Use more specific log message
166
231
  self.logger.info(
@@ -189,6 +254,48 @@ class MultiSourceAgentDeploymentService:
189
254
 
190
255
  return agents_by_name
191
256
 
257
+ def get_agents_by_collection(
258
+ self,
259
+ collection_id: str,
260
+ remote_agents_dir: Optional[Path] = None,
261
+ ) -> List[Dict[str, Any]]:
262
+ """Get all agents from a specific collection.
263
+
264
+ NEW: Enables collection-based agent selection.
265
+
266
+ Args:
267
+ collection_id: Collection identifier (e.g., "bobmatnyc/claude-mpm-agents")
268
+ remote_agents_dir: Directory containing remote agents cache
269
+
270
+ Returns:
271
+ List of agent dictionaries from the specified collection
272
+
273
+ Example:
274
+ >>> service = MultiSourceAgentDeploymentService()
275
+ >>> agents = service.get_agents_by_collection("bobmatnyc/claude-mpm-agents")
276
+ >>> len(agents)
277
+ 45
278
+ """
279
+ if not remote_agents_dir:
280
+ cache_dir = Path.home() / ".claude-mpm" / "cache"
281
+ remote_agents_dir = cache_dir / "remote-agents"
282
+
283
+ if not remote_agents_dir.exists():
284
+ self.logger.warning(
285
+ f"Remote agents directory not found: {remote_agents_dir}"
286
+ )
287
+ return []
288
+
289
+ # Use RemoteAgentDiscoveryService to get collection agents
290
+ remote_service = RemoteAgentDiscoveryService(remote_agents_dir)
291
+ collection_agents = remote_service.get_agents_by_collection(collection_id)
292
+
293
+ self.logger.info(
294
+ f"Retrieved {len(collection_agents)} agents from collection '{collection_id}'"
295
+ )
296
+
297
+ return collection_agents
298
+
192
299
  def select_highest_version_agents(
193
300
  self, agents_by_name: Dict[str, List[Dict[str, Any]]]
194
301
  ) -> Dict[str, Dict[str, Any]]:
@@ -32,6 +32,9 @@ class RemoteAgentMetadata:
32
32
  routing_priority: int
33
33
  source_file: Path
34
34
  version: str # SHA-256 hash from cache metadata
35
+ collection_id: Optional[str] = None # Format: owner/repo-name
36
+ source_path: Optional[str] = None # Relative path in repo
37
+ canonical_id: Optional[str] = None # Format: collection_id:agent_id
35
38
 
36
39
 
37
40
  class RemoteAgentDiscoveryService:
@@ -65,6 +68,91 @@ class RemoteAgentDiscoveryService:
65
68
  self.remote_agents_dir = remote_agents_dir
66
69
  self.logger = get_logger(__name__)
67
70
 
71
+ def _extract_collection_id_from_path(self, file_path: Path) -> Optional[str]:
72
+ """Extract collection_id from repository path structure.
73
+
74
+ Collection ID is derived from the repository path structure:
75
+ ~/.claude-mpm/cache/remote-agents/{owner}/{repo}/agents/...
76
+
77
+ Args:
78
+ file_path: Absolute path to agent Markdown file
79
+
80
+ Returns:
81
+ Collection ID in format "owner/repo-name" or None if not found
82
+
83
+ Example:
84
+ Input: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents/agents/pm.md
85
+ Output: "bobmatnyc/claude-mpm-agents"
86
+ """
87
+ try:
88
+ # Find "remote-agents" in the path
89
+ path_parts = file_path.parts
90
+ remote_agents_idx = -1
91
+
92
+ for i, part in enumerate(path_parts):
93
+ if part == "remote-agents":
94
+ remote_agents_idx = i
95
+ break
96
+
97
+ if remote_agents_idx == -1 or remote_agents_idx + 2 >= len(path_parts):
98
+ self.logger.debug(
99
+ f"Could not extract collection_id from path: {file_path}"
100
+ )
101
+ return None
102
+
103
+ # Extract owner and repo (next two parts after "remote-agents")
104
+ owner = path_parts[remote_agents_idx + 1]
105
+ repo = path_parts[remote_agents_idx + 2]
106
+
107
+ collection_id = f"{owner}/{repo}"
108
+ self.logger.debug(f"Extracted collection_id: {collection_id}")
109
+ return collection_id
110
+
111
+ except Exception as e:
112
+ self.logger.warning(
113
+ f"Failed to extract collection_id from {file_path}: {e}"
114
+ )
115
+ return None
116
+
117
+ def _extract_source_path_from_file(self, file_path: Path) -> Optional[str]:
118
+ """Extract relative source path within repository.
119
+
120
+ Source path is relative to the repository root (not the agents subdirectory).
121
+
122
+ Args:
123
+ file_path: Absolute path to agent Markdown file
124
+
125
+ Returns:
126
+ Relative path from repo root, or None if not found
127
+
128
+ Example:
129
+ Input: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents/agents/pm.md
130
+ Output: "agents/pm.md"
131
+ """
132
+ try:
133
+ # Find "remote-agents" in the path
134
+ path_parts = file_path.parts
135
+ remote_agents_idx = -1
136
+
137
+ for i, part in enumerate(path_parts):
138
+ if part == "remote-agents":
139
+ remote_agents_idx = i
140
+ break
141
+
142
+ if remote_agents_idx == -1 or remote_agents_idx + 3 >= len(path_parts):
143
+ return None
144
+
145
+ # Path after owner/repo is the source path
146
+ # remote-agents/{owner}/{repo}/{source_path}
147
+ repo_root_idx = remote_agents_idx + 3
148
+ source_parts = path_parts[repo_root_idx:]
149
+
150
+ return "/".join(source_parts)
151
+
152
+ except Exception as e:
153
+ self.logger.warning(f"Failed to extract source_path from {file_path}: {e}")
154
+ return None
155
+
68
156
  def _generate_hierarchical_id(self, file_path: Path) -> str:
69
157
  """Generate hierarchical agent ID from file path.
70
158
 
@@ -275,17 +363,34 @@ class RemoteAgentDiscoveryService:
275
363
  # Bug #1 fix: Detect category from directory path
276
364
  category = self._detect_category_from_path(md_file)
277
365
 
366
+ # NEW: Extract collection metadata from path
367
+ collection_id = self._extract_collection_id_from_path(md_file)
368
+ source_path = self._extract_source_path_from_file(md_file)
369
+
370
+ # NEW: Generate canonical_id (collection_id:agent_id)
371
+ if collection_id:
372
+ canonical_id = f"{collection_id}:{agent_id}"
373
+ else:
374
+ # Fallback for legacy agents without collection
375
+ canonical_id = f"legacy:{agent_id}"
376
+
278
377
  # Convert to JSON template format and return
279
378
  # IMPORTANT: Include 'path' field for compatibility with deployment validation (ticket 1M-480)
280
379
  # Git-sourced agents must have 'path' field to match structure from AgentDiscoveryService
281
380
  return {
282
381
  "agent_id": agent_id,
382
+ "canonical_id": canonical_id, # NEW: Primary matching key
383
+ "collection_id": collection_id, # NEW: Collection identifier
384
+ "source_path": source_path, # NEW: Path within repository
283
385
  "metadata": {
284
386
  "name": name,
285
387
  "description": description,
286
388
  "version": version,
287
389
  "author": "remote", # Mark as remote agent
288
390
  "category": category, # Use detected category from path
391
+ "collection_id": collection_id, # NEW: Also in metadata
392
+ "source_path": source_path, # NEW: Also in metadata
393
+ "canonical_id": canonical_id, # NEW: Also in metadata
289
394
  },
290
395
  "model": model,
291
396
  "source": "remote", # Mark as remote agent
@@ -347,7 +452,12 @@ class RemoteAgentDiscoveryService:
347
452
  Returns:
348
453
  RemoteAgentMetadata if found, None otherwise
349
454
  """
350
- for md_file in self.remote_agents_dir.glob("*.md"):
455
+ # Bug #4 fix: Search in /agents/ subdirectory, not root directory
456
+ agents_dir = self.remote_agents_dir / "agents"
457
+ if not agents_dir.exists():
458
+ return None
459
+
460
+ for md_file in agents_dir.rglob("*.md"):
351
461
  agent_dict = self._parse_markdown_agent(md_file)
352
462
  if agent_dict and agent_dict["metadata"]["name"] == agent_name:
353
463
  return RemoteAgentMetadata(
@@ -359,5 +469,89 @@ class RemoteAgentDiscoveryService:
359
469
  routing_priority=agent_dict["routing"]["priority"],
360
470
  source_file=Path(agent_dict["source_file"]),
361
471
  version=agent_dict["version"],
472
+ collection_id=agent_dict.get("collection_id"),
473
+ source_path=agent_dict.get("source_path"),
474
+ canonical_id=agent_dict.get("canonical_id"),
362
475
  )
363
476
  return None
477
+
478
+ def get_agents_by_collection(self, collection_id: str) -> List[Dict[str, Any]]:
479
+ """Get all agents belonging to a specific collection.
480
+
481
+ Args:
482
+ collection_id: Collection identifier in format "owner/repo-name"
483
+
484
+ Returns:
485
+ List of agent dictionaries from the specified collection
486
+
487
+ Example:
488
+ >>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/remote-agents"))
489
+ >>> agents = service.get_agents_by_collection("bobmatnyc/claude-mpm-agents")
490
+ >>> len(agents)
491
+ 45
492
+ """
493
+ all_agents = self.discover_remote_agents()
494
+
495
+ # Filter by collection_id
496
+ collection_agents = [
497
+ agent for agent in all_agents if agent.get("collection_id") == collection_id
498
+ ]
499
+
500
+ self.logger.info(
501
+ f"Found {len(collection_agents)} agents in collection '{collection_id}'"
502
+ )
503
+
504
+ return collection_agents
505
+
506
+ def list_collections(self) -> List[Dict[str, Any]]:
507
+ """List all available collections with agent counts.
508
+
509
+ Returns:
510
+ List of collection info dictionaries with:
511
+ - collection_id: Collection identifier
512
+ - agent_count: Number of agents in collection
513
+ - agents: List of agent IDs in collection
514
+
515
+ Example:
516
+ >>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/remote-agents"))
517
+ >>> collections = service.list_collections()
518
+ >>> collections
519
+ [
520
+ {
521
+ "collection_id": "bobmatnyc/claude-mpm-agents",
522
+ "agent_count": 45,
523
+ "agents": ["pm", "engineer", "qa", ...]
524
+ }
525
+ ]
526
+ """
527
+ all_agents = self.discover_remote_agents()
528
+
529
+ # Group by collection_id
530
+ collections_map: Dict[str, List[str]] = {}
531
+
532
+ for agent in all_agents:
533
+ collection_id = agent.get("collection_id")
534
+ if not collection_id:
535
+ # Skip agents without collection (legacy)
536
+ continue
537
+
538
+ if collection_id not in collections_map:
539
+ collections_map[collection_id] = []
540
+
541
+ agent_id = agent.get("agent_id", agent.get("metadata", {}).get("name"))
542
+ if agent_id:
543
+ collections_map[collection_id].append(agent_id)
544
+
545
+ # Convert to list format
546
+ collections = [
547
+ {
548
+ "collection_id": coll_id,
549
+ "agent_count": len(agent_ids),
550
+ "agents": sorted(agent_ids),
551
+ }
552
+ for coll_id, agent_ids in collections_map.items()
553
+ ]
554
+
555
+ self.logger.info(f"Found {len(collections)} collections")
556
+
557
+ return collections
@@ -188,10 +188,10 @@ class GitSourceSyncService:
188
188
 
189
189
  Args:
190
190
  source_url: Base URL for raw files (without trailing slash)
191
- cache_dir: Local cache directory (defaults to ~/.claude-mpm/cache/agents/)
191
+ cache_dir: Local cache directory (defaults to ~/.claude-mpm/cache/remote-agents/)
192
192
  source_id: Unique identifier for this source (for multi-source support)
193
193
 
194
- Design Decision: Cache to ~/.claude-mpm/cache/agents/ (Phase 1 of refactoring)
194
+ Design Decision: Cache to ~/.claude-mpm/cache/remote-agents/ (canonical location)
195
195
 
196
196
  Rationale: Separates cached repository structure from deployed agents.
197
197
  This allows preserving nested directory structure in cache while
@@ -207,13 +207,13 @@ class GitSourceSyncService:
207
207
  self.source_url = source_url.rstrip("/")
208
208
  self.source_id = source_id
209
209
 
210
- # Setup cache directory (Phase 1: Changed to ~/.claude-mpm/cache/agents/)
210
+ # Setup cache directory (canonical: ~/.claude-mpm/cache/remote-agents/)
211
211
  if cache_dir:
212
212
  self.cache_dir = Path(cache_dir)
213
213
  else:
214
- # Default to ~/.claude-mpm/cache/agents/ (shared cache for nested structure)
214
+ # Default to ~/.claude-mpm/cache/remote-agents/ (canonical cache location)
215
215
  home = Path.home()
216
- self.cache_dir = home / ".claude-mpm" / "cache" / "agents"
216
+ self.cache_dir = home / ".claude-mpm" / "cache" / "remote-agents"
217
217
 
218
218
  self.cache_dir.mkdir(parents=True, exist_ok=True)
219
219
 
@@ -237,6 +237,11 @@ class GitSourceSyncService:
237
237
  if etag_cache_file.exists():
238
238
  self._migrate_etag_cache(etag_cache_file)
239
239
 
240
+ # NEW: Initialize git manager for cache (Phase 1 integration)
241
+ from claude_mpm.services.agents.cache_git_manager import CacheGitManager
242
+
243
+ self.git_manager = CacheGitManager(self.cache_dir)
244
+
240
245
  def sync_agents(
241
246
  self,
242
247
  force_refresh: bool = False,
@@ -273,6 +278,33 @@ class GitSourceSyncService:
273
278
 
274
279
  start_time = time.time()
275
280
 
281
+ # NEW: Pre-sync git operations (Phase 1 integration)
282
+ if self.git_manager.is_git_repo():
283
+ logger.debug("Cache is a git repository, checking for updates...")
284
+
285
+ # Warn about uncommitted changes
286
+ if self.git_manager.has_uncommitted_changes():
287
+ uncommitted_count = len(
288
+ self.git_manager.get_status().get("uncommitted", [])
289
+ )
290
+ logger.warning(
291
+ f"Cache has {uncommitted_count} uncommitted change(s). "
292
+ "These will be preserved, but consider committing them."
293
+ )
294
+
295
+ # Pull latest if online (non-blocking)
296
+ try:
297
+ success, msg = self.git_manager.pull_latest()
298
+ if success:
299
+ logger.info(f"✅ Git pull: {msg}")
300
+ else:
301
+ logger.warning(f"⚠️ Git pull failed: {msg}")
302
+ logger.info("Continuing with HTTP sync as fallback")
303
+ except Exception as e:
304
+ logger.warning(f"Git pull error (continuing with HTTP sync): {e}")
305
+ else:
306
+ logger.debug("Cache is not a git repository, skipping git operations")
307
+
276
308
  results = {
277
309
  "synced": [],
278
310
  "cached": [],
@@ -0,0 +1,25 @@
1
+ """
2
+ Analysis services for Claude MPM.
3
+
4
+ Provides postmortem analysis and error improvement suggestions.
5
+ """
6
+
7
+ from .postmortem_service import (
8
+ ActionType,
9
+ ErrorAnalysis,
10
+ ErrorCategory,
11
+ ImprovementAction,
12
+ PostmortemReport,
13
+ PostmortemService,
14
+ get_postmortem_service,
15
+ )
16
+
17
+ __all__ = [
18
+ "ActionType",
19
+ "ErrorAnalysis",
20
+ "ErrorCategory",
21
+ "ImprovementAction",
22
+ "PostmortemReport",
23
+ "PostmortemService",
24
+ "get_postmortem_service",
25
+ ]