claude-mpm 0.3.0__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 (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,125 @@
1
+ """Example pre-delegation hook implementation."""
2
+
3
+ import json
4
+ from typing import Dict, Any, List
5
+
6
+ from claude_mpm.hooks.base_hook import PreDelegationHook, HookContext, HookResult
7
+ from claude_mpm.core.logger import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ class ContextFilterHook(PreDelegationHook):
13
+ """Hook that filters sensitive information from context before delegation."""
14
+
15
+ def __init__(self):
16
+ super().__init__(name="context_filter", priority=10)
17
+ self.sensitive_keys = {
18
+ 'api_key', 'secret', 'password', 'token',
19
+ 'private_key', 'credentials', 'auth'
20
+ }
21
+
22
+ def execute(self, context: HookContext) -> HookResult:
23
+ """Filter sensitive information from delegation context."""
24
+ try:
25
+ agent_context = context.data.get('context', {})
26
+ filtered_context = self._filter_sensitive(agent_context)
27
+
28
+ if filtered_context != agent_context:
29
+ logger.info("Filtered sensitive information from context")
30
+ return HookResult(
31
+ success=True,
32
+ data={
33
+ 'agent': context.data.get('agent'),
34
+ 'context': filtered_context
35
+ },
36
+ modified=True,
37
+ metadata={'filtered_keys': True}
38
+ )
39
+ else:
40
+ return HookResult(
41
+ success=True,
42
+ data=context.data,
43
+ modified=False
44
+ )
45
+
46
+ except Exception as e:
47
+ logger.error(f"Context filtering failed: {e}")
48
+ return HookResult(
49
+ success=False,
50
+ error=str(e)
51
+ )
52
+
53
+ def _filter_sensitive(self, data: Any) -> Any:
54
+ """Recursively filter sensitive keys from data."""
55
+ if isinstance(data, dict):
56
+ filtered = {}
57
+ for key, value in data.items():
58
+ if any(sensitive in key.lower() for sensitive in self.sensitive_keys):
59
+ filtered[key] = "[REDACTED]"
60
+ else:
61
+ filtered[key] = self._filter_sensitive(value)
62
+ return filtered
63
+ elif isinstance(data, list):
64
+ return [self._filter_sensitive(item) for item in data]
65
+ else:
66
+ return data
67
+
68
+
69
+ class AgentCapabilityEnhancerHook(PreDelegationHook):
70
+ """Hook that enhances agent context with additional capabilities."""
71
+
72
+ def __init__(self):
73
+ super().__init__(name="capability_enhancer", priority=30)
74
+ self.agent_enhancements = {
75
+ 'engineer': {
76
+ 'tools': ['code_analysis', 'refactoring', 'testing'],
77
+ 'context': 'You have access to advanced code analysis tools.'
78
+ },
79
+ 'researcher': {
80
+ 'tools': ['web_search', 'document_analysis'],
81
+ 'context': 'You can search the web and analyze documents.'
82
+ },
83
+ 'qa': {
84
+ 'tools': ['test_runner', 'coverage_analyzer'],
85
+ 'context': 'You have access to comprehensive testing tools.'
86
+ }
87
+ }
88
+
89
+ def execute(self, context: HookContext) -> HookResult:
90
+ """Enhance agent capabilities based on agent type."""
91
+ try:
92
+ agent_type = context.data.get('agent', '').lower()
93
+
94
+ if agent_type in self.agent_enhancements:
95
+ enhancement = self.agent_enhancements[agent_type]
96
+
97
+ # Add enhancements to context
98
+ enhanced_context = context.data.get('context', {}).copy()
99
+ enhanced_context['additional_tools'] = enhancement['tools']
100
+ enhanced_context['enhanced_context'] = enhancement['context']
101
+
102
+ logger.info(f"Enhanced {agent_type} agent with additional capabilities")
103
+
104
+ return HookResult(
105
+ success=True,
106
+ data={
107
+ 'agent': context.data.get('agent'),
108
+ 'context': enhanced_context
109
+ },
110
+ modified=True,
111
+ metadata={'enhancements_applied': True}
112
+ )
113
+ else:
114
+ return HookResult(
115
+ success=True,
116
+ data=context.data,
117
+ modified=False
118
+ )
119
+
120
+ except Exception as e:
121
+ logger.error(f"Capability enhancement failed: {e}")
122
+ return HookResult(
123
+ success=False,
124
+ error=str(e)
125
+ )
@@ -0,0 +1,100 @@
1
+ """Example submit hook implementation."""
2
+
3
+ import re
4
+ from typing import Dict, Any
5
+
6
+ from claude_mpm.hooks.base_hook import SubmitHook, HookContext, HookResult
7
+ from claude_mpm.core.logger import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ class TicketDetectionSubmitHook(SubmitHook):
13
+ """Hook that detects ticket references in user prompts."""
14
+
15
+ def __init__(self):
16
+ super().__init__(name="ticket_detection", priority=10)
17
+ self.ticket_pattern = re.compile(r'\b(?:TSK|BUG|FEAT)-\d+\b', re.IGNORECASE)
18
+
19
+ def execute(self, context: HookContext) -> HookResult:
20
+ """Detect and extract ticket references from prompt."""
21
+ try:
22
+ prompt = context.data.get('prompt', '')
23
+
24
+ # Find all ticket references
25
+ tickets = self.ticket_pattern.findall(prompt)
26
+
27
+ if tickets:
28
+ logger.info(f"Found {len(tickets)} ticket references: {tickets}")
29
+
30
+ # Add ticket references to metadata
31
+ return HookResult(
32
+ success=True,
33
+ data={
34
+ 'tickets': list(set(tickets)), # Unique tickets
35
+ 'prompt': prompt
36
+ },
37
+ modified=True,
38
+ metadata={'ticket_count': len(set(tickets))}
39
+ )
40
+ else:
41
+ return HookResult(
42
+ success=True,
43
+ data={'prompt': prompt},
44
+ modified=False
45
+ )
46
+
47
+ except Exception as e:
48
+ logger.error(f"Ticket detection failed: {e}")
49
+ return HookResult(
50
+ success=False,
51
+ error=str(e)
52
+ )
53
+
54
+
55
+ class PriorityDetectionSubmitHook(SubmitHook):
56
+ """Hook that detects priority indicators in prompts."""
57
+
58
+ def __init__(self):
59
+ super().__init__(name="priority_detection", priority=20)
60
+ self.priority_keywords = {
61
+ 'urgent': 'high',
62
+ 'asap': 'high',
63
+ 'critical': 'high',
64
+ 'important': 'high',
65
+ 'when you can': 'low',
66
+ 'whenever': 'low',
67
+ 'low priority': 'low'
68
+ }
69
+
70
+ def execute(self, context: HookContext) -> HookResult:
71
+ """Detect priority level from prompt."""
72
+ try:
73
+ prompt = context.data.get('prompt', '').lower()
74
+
75
+ # Check for priority keywords
76
+ detected_priority = 'normal'
77
+ for keyword, priority in self.priority_keywords.items():
78
+ if keyword in prompt:
79
+ detected_priority = priority
80
+ break
81
+
82
+ if detected_priority != 'normal':
83
+ logger.info(f"Detected priority: {detected_priority}")
84
+
85
+ return HookResult(
86
+ success=True,
87
+ data={
88
+ 'prompt': context.data.get('prompt', ''),
89
+ 'priority': detected_priority
90
+ },
91
+ modified=detected_priority != 'normal',
92
+ metadata={'priority_detected': detected_priority != 'normal'}
93
+ )
94
+
95
+ except Exception as e:
96
+ logger.error(f"Priority detection failed: {e}")
97
+ return HookResult(
98
+ success=False,
99
+ error=str(e)
100
+ )
@@ -0,0 +1,237 @@
1
+ """Example ticket extraction hook implementation."""
2
+
3
+ import re
4
+ from typing import Dict, Any, List, Optional
5
+ from datetime import datetime
6
+
7
+ from claude_mpm.hooks.base_hook import TicketExtractionHook, HookContext, HookResult
8
+ from claude_mpm.core.logger import get_logger
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class AutoTicketExtractionHook(TicketExtractionHook):
14
+ """Hook that automatically extracts tickets from conversations."""
15
+
16
+ def __init__(self):
17
+ super().__init__(name="auto_ticket_extraction", priority=10)
18
+
19
+ # Patterns for detecting ticket-worthy content
20
+ self.ticket_patterns = [
21
+ # Action items: "TODO:", "FIXME:", "ACTION:"
22
+ (r'(?:TODO|FIXME|ACTION):\s*(.+?)(?:\n|$)', 'action'),
23
+ # Bug reports: "bug:", "issue:", "problem:"
24
+ (r'(?:bug|issue|problem):\s*(.+?)(?:\n|$)', 'bug'),
25
+ # Feature requests: "feature:", "enhancement:", "request:"
26
+ (r'(?:feature|enhancement|request):\s*(.+?)(?:\n|$)', 'feature'),
27
+ # Questions that need follow-up
28
+ (r'(?:question|Q):\s*(.+?)(?:\n|$)', 'question'),
29
+ # Explicit ticket creation: "create ticket:", "new ticket:"
30
+ (r'(?:create ticket|new ticket):\s*(.+?)(?:\n|$)', 'ticket')
31
+ ]
32
+
33
+ def execute(self, context: HookContext) -> HookResult:
34
+ """Extract potential tickets from conversation."""
35
+ try:
36
+ # Get conversation content
37
+ content = context.data.get('content', '')
38
+ if isinstance(content, dict):
39
+ # Handle structured content
40
+ content = self._extract_text_content(content)
41
+
42
+ # Find all potential tickets
43
+ tickets = []
44
+
45
+ for pattern, ticket_type in self.ticket_patterns:
46
+ matches = re.finditer(pattern, content, re.IGNORECASE | re.MULTILINE)
47
+ for match in matches:
48
+ description = match.group(1).strip()
49
+ if description:
50
+ ticket = self._create_ticket(
51
+ description=description,
52
+ ticket_type=ticket_type,
53
+ context=context
54
+ )
55
+ tickets.append(ticket)
56
+
57
+ # Also check for numbered lists that might be tasks
58
+ numbered_tasks = re.findall(r'^\d+\.\s*(.+?)$', content, re.MULTILINE)
59
+ for task in numbered_tasks:
60
+ if self._is_actionable(task):
61
+ ticket = self._create_ticket(
62
+ description=task.strip(),
63
+ ticket_type='task',
64
+ context=context
65
+ )
66
+ tickets.append(ticket)
67
+
68
+ if tickets:
69
+ logger.info(f"Extracted {len(tickets)} potential tickets")
70
+ return HookResult(
71
+ success=True,
72
+ data={
73
+ 'tickets': tickets,
74
+ 'original_content': content
75
+ },
76
+ modified=True,
77
+ metadata={'ticket_count': len(tickets)}
78
+ )
79
+ else:
80
+ return HookResult(
81
+ success=True,
82
+ data={'original_content': content},
83
+ modified=False
84
+ )
85
+
86
+ except Exception as e:
87
+ logger.error(f"Ticket extraction failed: {e}")
88
+ return HookResult(
89
+ success=False,
90
+ error=str(e)
91
+ )
92
+
93
+ def _extract_text_content(self, data: Any) -> str:
94
+ """Extract text content from structured data."""
95
+ if isinstance(data, str):
96
+ return data
97
+ elif isinstance(data, dict):
98
+ # Try common keys
99
+ for key in ['content', 'text', 'message', 'result']:
100
+ if key in data:
101
+ return self._extract_text_content(data[key])
102
+ # Fallback to string representation
103
+ return str(data)
104
+ elif isinstance(data, list):
105
+ return '\n'.join(self._extract_text_content(item) for item in data)
106
+ else:
107
+ return str(data)
108
+
109
+ def _is_actionable(self, text: str) -> bool:
110
+ """Determine if text represents an actionable item."""
111
+ actionable_verbs = [
112
+ 'implement', 'create', 'add', 'fix', 'update', 'remove',
113
+ 'test', 'verify', 'check', 'investigate', 'research',
114
+ 'document', 'write', 'review', 'refactor', 'optimize'
115
+ ]
116
+
117
+ text_lower = text.lower()
118
+ return any(verb in text_lower for verb in actionable_verbs)
119
+
120
+ def _create_ticket(self, description: str, ticket_type: str,
121
+ context: HookContext) -> Dict[str, Any]:
122
+ """Create a ticket structure."""
123
+ return {
124
+ 'id': None, # To be assigned by ticket system
125
+ 'title': self._generate_title(description),
126
+ 'description': description,
127
+ 'type': ticket_type,
128
+ 'priority': context.data.get('priority', 'normal'),
129
+ 'status': 'pending',
130
+ 'created_at': datetime.now().isoformat(),
131
+ 'source': 'auto_extraction',
132
+ 'metadata': {
133
+ 'session_id': context.session_id,
134
+ 'user_id': context.user_id,
135
+ 'extraction_timestamp': context.timestamp.isoformat()
136
+ }
137
+ }
138
+
139
+ def _generate_title(self, description: str) -> str:
140
+ """Generate a concise title from description."""
141
+ # Take first 50 chars or up to first period/newline
142
+ title = description[:50]
143
+
144
+ # Try to cut at sentence boundary
145
+ for delimiter in ['.', '\n', '!', '?']:
146
+ if delimiter in title:
147
+ title = title.split(delimiter)[0]
148
+ break
149
+
150
+ # Clean up and ensure not too long
151
+ title = title.strip()
152
+ if len(title) > 50:
153
+ title = title[:47] + '...'
154
+
155
+ return title
156
+
157
+
158
+ class TicketPriorityAnalyzerHook(TicketExtractionHook):
159
+ """Hook that analyzes and assigns priority to extracted tickets."""
160
+
161
+ def __init__(self):
162
+ super().__init__(name="ticket_priority_analyzer", priority=50)
163
+
164
+ self.priority_indicators = {
165
+ 'critical': ['critical', 'urgent', 'blocker', 'emergency', 'asap'],
166
+ 'high': ['important', 'high priority', 'needed', 'required'],
167
+ 'low': ['minor', 'nice to have', 'someday', 'optional']
168
+ }
169
+
170
+ def execute(self, context: HookContext) -> HookResult:
171
+ """Analyze and update ticket priorities."""
172
+ try:
173
+ tickets = context.data.get('tickets', [])
174
+
175
+ if not tickets:
176
+ return HookResult(
177
+ success=True,
178
+ data=context.data,
179
+ modified=False
180
+ )
181
+
182
+ # Analyze each ticket
183
+ updated_tickets = []
184
+ priorities_updated = 0
185
+
186
+ for ticket in tickets:
187
+ original_priority = ticket.get('priority', 'normal')
188
+ analyzed_priority = self._analyze_priority(ticket)
189
+
190
+ if analyzed_priority != original_priority:
191
+ ticket['priority'] = analyzed_priority
192
+ ticket['metadata']['priority_analyzed'] = True
193
+ priorities_updated += 1
194
+
195
+ updated_tickets.append(ticket)
196
+
197
+ if priorities_updated > 0:
198
+ logger.info(f"Updated priority for {priorities_updated} tickets")
199
+ return HookResult(
200
+ success=True,
201
+ data={
202
+ 'tickets': updated_tickets
203
+ },
204
+ modified=True,
205
+ metadata={'priorities_updated': priorities_updated}
206
+ )
207
+ else:
208
+ return HookResult(
209
+ success=True,
210
+ data=context.data,
211
+ modified=False
212
+ )
213
+
214
+ except Exception as e:
215
+ logger.error(f"Priority analysis failed: {e}")
216
+ return HookResult(
217
+ success=False,
218
+ error=str(e)
219
+ )
220
+
221
+ def _analyze_priority(self, ticket: Dict[str, Any]) -> str:
222
+ """Analyze ticket content to determine priority."""
223
+ content = f"{ticket.get('title', '')} {ticket.get('description', '')}".lower()
224
+
225
+ # Check for priority indicators
226
+ for priority, indicators in self.priority_indicators.items():
227
+ if any(indicator in content for indicator in indicators):
228
+ return priority
229
+
230
+ # Check ticket type
231
+ ticket_type = ticket.get('type', '')
232
+ if ticket_type == 'bug':
233
+ return 'high'
234
+ elif ticket_type == 'question':
235
+ return 'normal'
236
+
237
+ return 'normal'