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.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- 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'
|